blob: 025fb82f18c31976198c638a3c6c675e2a43337c [file] [log] [blame]
/*
* Copyright (c) 2021, Alliance for Open Media. All rights reserved
*
* This source code is subject to the terms of the BSD 3-Clause Clear License
* and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
* License was not distributed with this source code in the LICENSE file, you
* can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the
* Alliance for Open Media Patent License 1.0 was not distributed with this
* source code in the PATENTS file, you can obtain it at
* aomedia.org/license/patent-license/.
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "av1/encoder/subgop.h"
static char *my_strtok_r(char *str, const char *delim, char **saveptr) {
if (str == NULL) return NULL;
if (strlen(str) == 0) return NULL;
char *ptr = str;
char *x = strstr(str, delim);
if (x) {
*x = 0;
if (saveptr) *saveptr = x + strlen(delim);
} else {
if (saveptr) *saveptr = NULL;
return ptr;
}
return ptr;
}
static char *read_token_after(char *str, const char *delim, char **saveptr) {
if (str == NULL) return NULL;
if (strlen(str) == 0) return NULL;
char *ptr = str;
char *x = strstr(str, delim);
if (x) {
ptr = x + strlen(delim);
while (*x != 0 && !isspace(*x)) x++;
*x = 0;
if (saveptr) *saveptr = x + 1;
return ptr;
} else {
if (saveptr) *saveptr = str;
return NULL;
}
}
static bool readline(char *buf, int size, FILE *fp) {
buf[0] = '\0';
buf[size - 1] = '\0';
char *tmp;
while (1) {
if (fgets(buf, size, fp) == NULL) {
*buf = '\0';
return false;
} else {
if ((tmp = strrchr(buf, '\n')) != NULL) *tmp = '\0';
if ((tmp = strchr(buf, '#')) != NULL) *tmp = '\0';
for (int i = 0; i < (int)strlen(buf); ++i) {
if (!isspace(buf[i])) return true;
}
}
}
return true;
}
void av1_init_subgop_config_set(SubGOPSetCfg *config_set) {
memset(config_set, 0, sizeof(*config_set));
config_set->num_configs = 0;
for (int i = 0; i < MAX_SUBGOP_CONFIGS; ++i) {
memset(&config_set->config[i], 0, sizeof(config_set->config[i]));
}
}
static void check_duplicate_and_add(SubGOPSetCfg *config_set) {
int k;
const int n = config_set->num_configs;
for (k = 0; k < n; ++k) {
if (config_set->config[k].num_frames == config_set->config[n].num_frames &&
config_set->config[k].subgop_in_gop_code ==
config_set->config[n].subgop_in_gop_code) {
memcpy(&config_set->config[k], &config_set->config[n],
sizeof(config_set->config[k]));
return;
}
}
if (k == n) config_set->num_configs++;
}
static int process_subgop_step(char *str, SubGOPStepCfg *step) {
char *ptr;
step->num_references = 0;
step->disp_frame_idx = (int8_t)strtol(str, &ptr, 10);
// Check if no numeric disp idx exist or is negative
if (ptr == str || step->disp_frame_idx < 0) return 0;
switch (*ptr) {
case 'V': step->type_code = FRAME_TYPE_INO_VISIBLE; break;
case 'R': step->type_code = FRAME_TYPE_INO_REPEAT; break;
case 'S': step->type_code = FRAME_TYPE_INO_SHOWEXISTING; return 1;
case 'F': step->type_code = FRAME_TYPE_OOO_FILTERED; break;
case 'U': step->type_code = FRAME_TYPE_OOO_UNFILTERED; break;
default: return 0;
}
str = ++ptr;
step->pyr_level = (int8_t)strtol(str, &ptr, 10);
// Check if no numeric disp idx exist
if (ptr == str) return 0;
assert(ptr != NULL);
// Check for character P preceding the references.
if (*ptr == 'P') {
str = ++ptr;
char delim[] = "^";
char *token, *next = NULL;
while ((token = my_strtok_r(str, delim, &str)) != NULL) {
if (strlen(token) == 0) return 0;
if (step->num_references >= INTER_REFS_PER_FRAME) return 0;
step->references[step->num_references] = (int8_t)strtol(token, &next, 10);
if (step->references[step->num_references] == 0)
return 0; // 0 is invalid
step->num_references++;
}
ptr = next;
} else {
// Unspecified references
step->num_references = -1;
}
assert(ptr != NULL);
step->refresh = -1;
if (*ptr == 'X') {
ptr++;
step->refresh = (int8_t)strtol(ptr, NULL, 10);
if (step->refresh < 0) return 0;
} else if (*ptr == 0) {
// no refresh data preceded by X provided
return 1;
} else {
return 0;
}
return 1;
}
static int process_subgop_steps(char *str, SubGOPCfg *config) {
char delim[] = "/";
config->num_steps = 0;
char *token;
while ((token = my_strtok_r(str, delim, &str)) != NULL) {
if (strlen(token) == 0) return 0;
int res = process_subgop_step(token, &config->step[config->num_steps]);
if (!res) return 0;
// Populate pyr level for show existing frame
if (config->step[config->num_steps].type_code ==
FRAME_TYPE_INO_SHOWEXISTING) {
int k;
for (k = 0; k < config->num_steps; ++k) {
if (config->step[k].disp_frame_idx ==
config->step[config->num_steps].disp_frame_idx) {
config->step[config->num_steps].pyr_level = config->step[k].pyr_level;
break;
}
}
// showexisting for a frame not coded before is invalid
if (k == config->num_steps) return 0;
}
config->num_steps++;
}
return 1;
}
static int process_subgop_config(char *str, SubGOPCfg *config) {
if (strlen(str) == 0) return 0;
char delim[] = ":";
char *token = my_strtok_r(str, delim, &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
config->num_frames = atoi(token);
token = my_strtok_r(str, delim, &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
int subgop_in_gop_code = atoi(token);
// check for invalid subgop_in_gop_code
if (subgop_in_gop_code < 0 || subgop_in_gop_code >= SUBGOP_IN_GOP_CODES)
return 0;
config->subgop_in_gop_code = (SUBGOP_IN_GOP_CODE)subgop_in_gop_code;
token = my_strtok_r(str, delim, &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
return process_subgop_steps(token, config);
}
static int is_visible(FRAME_TYPE_CODE code) {
switch (code) {
case FRAME_TYPE_INO_VISIBLE:
case FRAME_TYPE_INO_REPEAT:
case FRAME_TYPE_INO_SHOWEXISTING: return 1;
case FRAME_TYPE_OOO_FILTERED:
case FRAME_TYPE_OOO_UNFILTERED: return 0;
default: assert(0 && "Invalid frame type code"); return 0;
}
}
static int check_subgop_config(SubGOPCfg *config) {
// check for invalid disp_frame_idx
for (int s = 0; s < config->num_steps; ++s) {
if (config->step[s].disp_frame_idx > config->num_frames) return 0;
}
// Each disp frame index must be shown exactly once and in ascending order
int last_visible = 0;
for (int s = 0; s < config->num_steps; ++s) {
if (is_visible(config->step[s].type_code)) {
if (config->step[s].disp_frame_idx != last_visible + 1) return 0;
last_visible = config->step[s].disp_frame_idx;
}
}
if (last_visible != config->num_frames) return 0;
// Each disp frame index must have at most one invisible frame
int invisible[MAX_SUBGOP_LENGTH];
memset(invisible, 0, config->num_frames * sizeof(*invisible));
for (int s = 0; s < config->num_steps; ++s) {
if (!is_visible(config->step[s].type_code))
invisible[config->step[s].disp_frame_idx - 1]++;
}
for (int k = 0; k < config->num_frames; ++k) {
if (invisible[k] > 1) return 0;
}
return 1;
}
int av1_process_subgop_config_set(const char *param, SubGOPSetCfg *config_set) {
if (!param) return 1;
if (!strlen(param)) return 1;
const int bufsize = (int)((strlen(param) + 1) * sizeof(*param));
char *buf = (char *)aom_malloc(bufsize);
memcpy(buf, param, bufsize);
char delim[] = ",";
char *str = buf;
char *token;
while ((token = my_strtok_r(str, delim, &str)) != NULL) {
int res = process_subgop_config(
token, &config_set->config[config_set->num_configs]);
if (res) {
res = check_subgop_config(&config_set->config[config_set->num_configs]);
if (res) {
check_duplicate_and_add(config_set);
} else {
printf(
"Warning: Subgop config validation failed for config #%d, "
"skipping the rest.\n",
config_set->num_configs);
return 0;
}
} else {
printf(
"Warning: Subgop config parsing failed for config #%d, "
"skipping the rest.\n",
config_set->num_configs);
return 0;
}
}
aom_free(buf);
return 1;
}
static int process_subgop_config_fromfile(FILE *fp, SubGOPCfg *config) {
char line[256];
int linesize = 256;
int s;
if (!readline(line, linesize, fp)) return 0;
char *token;
char *str = line;
token = read_token_after(str, "num_frames:", &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
const int num_frames = atoi(token);
if (num_frames <= 0) return 0;
config->num_frames = num_frames;
token = read_token_after(str, "subgop_in_gop_code:", &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
const int subgop_in_gop_code = atoi(token);
// check for invalid subgop_in_gop_code
if (subgop_in_gop_code < 0 || subgop_in_gop_code >= SUBGOP_IN_GOP_CODES)
return 0;
config->subgop_in_gop_code = (SUBGOP_IN_GOP_CODE)subgop_in_gop_code;
token = read_token_after(str, "num_steps:", &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
config->num_steps = atoi(token);
for (s = 0; s < config->num_steps; ++s) {
SubGOPStepCfg *step = &config->step[s];
step->num_references = 0;
if (!readline(line, linesize, fp)) return 0;
str = line;
token = read_token_after(str, "disp_frame_idx:", &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
const int disp_frame_idx = atoi(token);
if (disp_frame_idx < 0 || disp_frame_idx > config->num_frames) return 0;
step->disp_frame_idx = disp_frame_idx;
token = read_token_after(str, "type_code:", &str);
if (!token) return 0;
if (strlen(token) != 1) return 0;
switch (*token) {
case 'V': step->type_code = FRAME_TYPE_INO_VISIBLE; break;
case 'R': step->type_code = FRAME_TYPE_INO_REPEAT; break;
case 'S': step->type_code = FRAME_TYPE_INO_SHOWEXISTING; break;
case 'F': step->type_code = FRAME_TYPE_OOO_FILTERED; break;
case 'U': step->type_code = FRAME_TYPE_OOO_UNFILTERED; break;
default: return 0;
}
if (step->type_code == FRAME_TYPE_INO_SHOWEXISTING) {
int k;
for (k = 0; k < s; ++k) {
if (config->step[k].disp_frame_idx == step->disp_frame_idx) {
step->pyr_level = config->step[k].pyr_level;
break;
}
}
// showexisting for a frame not coded before is invalid
if (k == s) return 0;
continue;
}
token = read_token_after(str, "pyr_level:", &str);
if (!token) return 0;
if (strlen(token) == 0) return 0;
const int pyr_level = atoi(token);
if (pyr_level <= 0) return 0;
step->pyr_level = pyr_level;
token = read_token_after(str, "references:", &str);
if (!token) { // no references specified
step->num_references = -1;
} else if (strlen(token) > 0) {
char delim[] = "^";
char *ptr = token;
while ((token = my_strtok_r(ptr, delim, &ptr)) != NULL) {
if (strlen(token) == 0) return 0;
if (step->num_references >= INTER_REFS_PER_FRAME) return 0;
step->references[step->num_references] =
(int8_t)strtol(token, NULL, 10);
step->num_references++;
}
}
step->refresh = -1;
token = read_token_after(str, "refresh:", &str);
if (!token) continue;
if (strlen(token) == 0) continue;
const int refresh = atoi(token);
if (refresh < 0) return 0;
step->refresh = refresh;
}
return 1;
}
int av1_process_subgop_config_set_fromfile(const char *paramfile,
SubGOPSetCfg *config_set) {
if (!paramfile) return 1;
if (!strlen(paramfile)) return 1;
FILE *fp = fopen(paramfile, "r");
if (!fp) return 0;
char line[256];
int linesize = 256;
while (readline(line, linesize, fp)) {
if (read_token_after(line, "config:", NULL)) {
int res = process_subgop_config_fromfile(
fp, &config_set->config[config_set->num_configs]);
if (res) {
res = check_subgop_config(&config_set->config[config_set->num_configs]);
if (res) {
check_duplicate_and_add(config_set);
} else {
printf(
"Warning: Subgop config validation failed for config #%d, "
"skipping the rest.\n",
config_set->num_configs);
fclose(fp);
return 0;
}
} else {
printf("Warning: config parsing failed, skipping the rest.\n");
fclose(fp);
return 0;
}
} else {
printf("Warning: config not found, skipping the rest.\n");
fclose(fp);
return 0;
}
}
fclose(fp);
return 1;
}
void av1_print_subgop_config_set(SubGOPSetCfg *config_set) {
if (!config_set->num_configs) return;
printf("#SUBGOP CONFIG SET\n");
printf("##################\n");
printf("#num_configs:%d\n", config_set->num_configs);
for (int i = 0; i < config_set->num_configs; ++i) {
printf("config:#%d\n", i);
SubGOPCfg *config = &config_set->config[i];
printf(" num_frames:%d", config->num_frames);
printf(" subgop_in_gop_code:%d", config->subgop_in_gop_code);
printf(" num_steps:%d\n", config->num_steps);
for (int j = 0; j < config->num_steps; ++j) {
printf(" [step:%d]", j);
printf(" disp_frame_idx:%d", config->step[j].disp_frame_idx);
printf(" type_code:%c", config->step[j].type_code);
printf(" pyr_level:%d", config->step[j].pyr_level);
if (config->step[j].type_code != FRAME_TYPE_INO_SHOWEXISTING &&
config->step[j].num_references >= 0) {
printf(" references:");
for (int r = 0; r < config->step[j].num_references; ++r) {
if (r) printf("^");
printf("%d", config->step[j].references[r]);
}
}
if (config->step[j].refresh >= 0)
printf(" refresh:%d", config->step[j].refresh);
printf("\n");
}
}
printf("\n");
}
const SubGOPCfg *av1_find_subgop_config(SubGOPSetCfg *config_set,
int num_frames, int is_last_subgop,
int is_first_subgop) {
SubGOPCfg *cfg = NULL;
SUBGOP_IN_GOP_CODE subgop_in_gop_code;
if (is_last_subgop)
subgop_in_gop_code = SUBGOP_IN_GOP_LAST;
else if (is_first_subgop)
subgop_in_gop_code = SUBGOP_IN_GOP_FIRST;
else
subgop_in_gop_code = SUBGOP_IN_GOP_GENERIC;
for (int i = 0; i < config_set->num_configs; ++i) {
if (config_set->config[i].num_frames == num_frames) {
if (config_set->config[i].subgop_in_gop_code == subgop_in_gop_code)
return &config_set->config[i];
else if (config_set->config[i].subgop_in_gop_code ==
SUBGOP_IN_GOP_GENERIC)
cfg = &config_set->config[i];
}
}
return cfg;
}
const SubGOPCfg *av1_find_subgop_config_exact(
SubGOPSetCfg *config_set, int num_frames,
SUBGOP_IN_GOP_CODE subgop_in_gop_code) {
for (int i = 0; i < config_set->num_configs; ++i) {
if (config_set->config[i].num_frames == num_frames) {
if (config_set->config[i].subgop_in_gop_code == subgop_in_gop_code)
return &config_set->config[i];
}
}
return NULL;
}