/*****************************************************************************
* This file is part of Kvazaar HEVC encoder.
*
* Copyright (C) 2013-2015 Tampere University of Technology and others (see
* COPYING file).
*
* Kvazaar is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* Kvazaar is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with Kvazaar. If not, see .
****************************************************************************/
/*
* \file
*/
#include "config.h"
#include
#include
#include
#include
/**
* \brief Allocate memory for config object
* \return pointer to allocated memory
*/
config_t *config_alloc(void)
{
config_t *cfg = (config_t *)malloc(sizeof(config_t));
if (!cfg) {
fprintf(stderr, "Failed to allocate a config object!\n");
return cfg;
}
FILL(*cfg, 0);
return cfg;
}
/**
* \brief Initialize config structure
* \param cfg config object
* \return 1 on success, 0 on failure
*/
int config_init(config_t *cfg)
{
cfg->input = NULL;
cfg->output = NULL;
cfg->debug = NULL;
cfg->frames = 0;
cfg->width = 0;
cfg->height = 0;
cfg->qp = 32;
cfg->intra_period = 0;
cfg->vps_period = 0;
cfg->deblock_enable = 1;
cfg->deblock_beta = 0;
cfg->deblock_tc = 0;
cfg->sao_enable = 1;
cfg->rdoq_enable = 1;
cfg->signhide_enable = true;
cfg->rdo = 1;
cfg->full_intra_search = 0;
cfg->trskip_enable = 1;
cfg->tr_depth_intra = 0;
cfg->fme_level = 1;
cfg->vui.sar_width = 0;
cfg->vui.sar_height = 0;
cfg->vui.overscan = 0; /* undef */
cfg->vui.videoformat = 5; /* undef */
cfg->vui.fullrange = 0; /* limited range */
cfg->vui.colorprim = 2; /* undef */
cfg->vui.transfer = 2; /* undef */
cfg->vui.colormatrix = 2; /* undef */
cfg->vui.chroma_loc = 0; /* left center */
cfg->aud_enable = 0;
cfg->cqmfile = NULL;
cfg->ref_frames = DEFAULT_REF_PIC_COUNT;
cfg->seek = 0;
cfg->tiles_width_count = 0;
cfg->tiles_height_count = 0;
cfg->tiles_width_split = NULL;
cfg->tiles_height_split = NULL;
cfg->wpp = 0;
cfg->owf = -1;
cfg->slice_count = 1;
cfg->slice_addresses_in_ts = MALLOC(int32_t, 1);
cfg->slice_addresses_in_ts[0] = 0;
cfg->threads = 0;
cfg->cpuid = 1;
// Defaults for what sizes of PUs are tried.
cfg->pu_depth_inter.min = 0; // 0-3
cfg->pu_depth_inter.max = 3; // 0-3
cfg->pu_depth_intra.min = 1; // 0-4
cfg->pu_depth_intra.max = 4; // 0-4
return 1;
}
/**
* \brief Free memory allocated to the config
* \param cfg config object
* \return 1 on success, 0 on failure
*/
int config_destroy(config_t *cfg)
{
FREE_POINTER(cfg->input);
FREE_POINTER(cfg->output);
FREE_POINTER(cfg->cqmfile);
FREE_POINTER(cfg->tiles_width_split);
FREE_POINTER(cfg->tiles_height_split);
FREE_POINTER(cfg->slice_addresses_in_ts);
free(cfg);
return 1;
}
/**
* \brief Allocates memory space for a string, and copies it
* \param char * string to copy
* \return a pointer to the copied string on success, null on failure
*/
static char *copy_string(const char *string)
{
// Allocate +1 for \0
char *allocated_string = (char *)malloc(strlen(string) + 1);
if (!allocated_string) {
fprintf(stderr, "Failed to allocate a string!\n");
return allocated_string;
}
// Copy the string to the new buffer
memcpy(allocated_string, string, strlen(string) + 1);
return allocated_string;
}
static int atobool(const char *str)
{
if (!strcmp(str, "1") ||
!strcmp(str, "true") ||
!strcmp(str, "yes"))
return 1;
if (!strcmp(str, "0") ||
!strcmp(str, "false") ||
!strcmp(str, "no"))
return 0;
return 0;
}
static int parse_enum(const char *arg, const char * const *names, int8_t *dst)
{
int8_t i;
for (i = 0; names[i]; i++) {
if (!strcmp(arg, names[i])) {
*dst = i;
return 1;
}
}
return 0;
}
static int parse_tiles_specification(const char* const arg, int32_t * const ntiles, int32_t** const array) {
const char* current_arg = NULL;
int32_t current_value;
int32_t values[MAX_TILES_PER_DIM];
int i;
//Free pointer in any case
if (*array) {
FREE_POINTER(*array);
}
//If the arg starts with u, we want an uniform split
if (arg[0]=='u') {
*ntiles = atoi(arg+1)-1;
if (MAX_TILES_PER_DIM <= *ntiles || 0 >= *ntiles) {
fprintf(stderr, "Invalid number of tiles (0 < %d <= %d = MAX_TILES_PER_DIM)!\n", *ntiles + 1, MAX_TILES_PER_DIM);
return 0;
}
//Done with parsing
return 1;
}
//We have a comma-separated list of int for the split...
current_arg = arg;
*ntiles = 0;
do {
int ret = sscanf(current_arg, "%d", ¤t_value);
if (ret != 1) {
fprintf(stderr, "Could not parse integer \"%s\"!\n", current_arg);
return 0;
}
current_arg = strchr(current_arg, ',');
//Skip the , if we found one
if (current_arg) ++current_arg;
values[*ntiles] = current_value;
++(*ntiles);
if (MAX_TILES_PER_DIM <= *ntiles) break;
} while (current_arg);
if (MAX_TILES_PER_DIM <= *ntiles || 0 >= *ntiles) {
fprintf(stderr, "Invalid number of tiles (0 < %d <= %d = MAX_TILES_PER_DIM)!\n", *ntiles + 1, MAX_TILES_PER_DIM);
return 0;
}
*array = MALLOC(int32_t, *ntiles);
if (!*array) {
fprintf(stderr, "Could not allocate array for tiles\n");
return 0;
}
//TODO: memcpy?
for (i = 0; i < *ntiles; ++i) {
(*array)[i] = values[i];
}
return 1;
}
static int parse_slice_specification(const char* const arg, int32_t * const nslices, int32_t** const array) {
const char* current_arg = NULL;
int32_t current_value;
int32_t values[MAX_SLICES];
int i;
//Free pointer in any case
if (*array) {
FREE_POINTER(*array);
}
//If the arg starts with u, we want an uniform split
if (arg[0]=='u') {
*nslices = atoi(arg+1);
if (MAX_SLICES <= *nslices || 0 >= *nslices) {
fprintf(stderr, "Invalid number of tiles (0 < %d <= %d = MAX_SLICES)!\n", *nslices + 1, MAX_SLICES);
return 0;
}
//Done with parsing
return 1;
}
//We have a comma-separated list of int for the split...
current_arg = arg;
//We always have a slice starting at 0
values[0] = 0;
*nslices = 1;
do {
int ret = sscanf(current_arg, "%d", ¤t_value);
if (ret != 1) {
fprintf(stderr, "Could not parse integer \"%s\"!\n", current_arg);
return 0;
}
current_arg = strchr(current_arg, ',');
//Skip the , if we found one
if (current_arg) ++current_arg;
values[*nslices] = current_value;
++(*nslices);
if (MAX_SLICES <= *nslices) break;
} while (current_arg);
if (MAX_SLICES <= *nslices || 0 >= *nslices) {
fprintf(stderr, "Invalid number of slices (0 < %d <= %d = MAX_SLICES)!\n", *nslices, MAX_SLICES);
return 0;
}
*array = MALLOC(int32_t, *nslices);
if (!*array) {
fprintf(stderr, "Could not allocate array for slices\n");
return 0;
}
//TODO: memcpy?
for (i = 0; i < *nslices; ++i) {
(*array)[i] = values[i];
}
return 1;
}
static int config_parse(config_t *cfg, const char *name, const char *value)
{
static const char * const overscan_names[] = { "undef", "show", "crop", NULL };
static const char * const videoformat_names[] = { "component", "pal", "ntsc", "secam", "mac", "undef", NULL };
static const char * const range_names[] = { "tv", "pc", NULL };
static const char * const colorprim_names[] = { "", "bt709", "undef", "", "bt470m", "bt470bg", "smpte170m",
"smpte240m", "film", "bt2020", NULL };
static const char * const transfer_names[] = { "", "bt709", "undef", "", "bt470m", "bt470bg", "smpte170m",
"smpte240m", "linear", "log100", "log316", "iec61966-2-4",
"bt1361e", "iec61966-2-1", "bt2020-10", "bt2020-12", NULL };
static const char * const colormatrix_names[] = { "GBR", "bt709", "undef", "", "fcc", "bt470bg", "smpte170m",
"smpte240m", "YCgCo", "bt2020nc", "bt2020c", NULL };
int error = 0;
if (!name)
return 0;
if (!value)
value = "true";
// Treat "--no-param" as --param 0
if ((!strncmp(name, "no-", 3))) {
name += 3;
value = atobool(value) ? "false" : "true";
}
#define OPT(STR) (!strcmp(name, STR))
if OPT("input")
cfg->input = copy_string(value);
else if OPT("output")
cfg->output = copy_string(value);
else if OPT("debug")
cfg->debug = copy_string(value);
else if OPT("width")
cfg->width = atoi(value);
else if OPT("height")
cfg->height = atoi(value);
else if OPT("input-res") {
if (2 != sscanf(value, "%dx%d", &cfg->width, &cfg->height)) {
cfg->width = cfg->height = 0;
}
}
else if OPT("frames")
cfg->frames = atoi(value);
else if OPT("qp")
cfg->qp = atoi(value);
else if OPT("period")
cfg->intra_period = atoi(value);
else if OPT("vps-period")
cfg->vps_period = atoi(value);
else if OPT("ref") {
cfg->ref_frames = atoi(value);
if (cfg->ref_frames < 1 || cfg->ref_frames >= MAX_REF_PIC_COUNT) {
fprintf(stderr, "--ref out of range [1..15], set to 3\n");
cfg->ref_frames = 3;
}
}
else if OPT("deblock") {
int beta, tc;
if (2 == sscanf(value, "%d:%d", &beta, &tc)) {
cfg->deblock_enable = 1;
cfg->deblock_beta = beta;
cfg->deblock_tc = tc;
} else if (sscanf(value, "%d", &beta)) {
cfg->deblock_enable = 1;
cfg->deblock_beta = beta;
cfg->deblock_tc = cfg->deblock_beta;
} else
cfg->deblock_enable = atobool(value);
if (cfg->deblock_beta < -6 || cfg->deblock_beta > 6) {
fprintf(stderr, "--deblock beta parameter out of range [-6..6], set to 0\n");
cfg->deblock_beta = 0;
}
if (cfg->deblock_tc < -6 || cfg->deblock_tc > 6) {
fprintf(stderr, "--deblock tc parameter out of range [-6..6], set to 0\n");
cfg->deblock_tc = 0;
}
}
else if OPT("sao")
cfg->sao_enable = atobool(value);
else if OPT("rdoq")
cfg->rdoq_enable = atobool(value);
else if OPT("signhide")
cfg->signhide_enable = (bool)atobool(value);
else if OPT("rd")
{
int rdo = 0;
if (sscanf(value, "%d", &rdo)) {
if (rdo < 0 || rdo > 3) {
fprintf(stderr, "--rd parameter out of range [0..2], set to 1\n");
rdo = 1;
}
cfg->rdo = rdo;
}
}
else if OPT("full-intra-search")
cfg->full_intra_search = atobool(value);
else if OPT("transform-skip")
cfg->trskip_enable = atobool(value);
else if OPT("tr-depth-intra") {
cfg->tr_depth_intra = atoi(value);
if (cfg->tr_depth_intra == 0 && strcmp(value, "0")) {
error = 1;
} else if (cfg->tr_depth_intra < 0 || cfg->tr_depth_intra > 4) {
// range is 0 .. CtbLog2SizeY - Log2MinTrafoSize
error = 1;
}
}
else if OPT("subme") {
cfg->fme_level = atoi(value);
if (cfg->fme_level != 0 && cfg->fme_level != 1) {
error = 1;
fprintf(stderr, "Invalid --subme parameter (must be 0 or 1).\n");
}
}
else if OPT("sar") {
int sar_width, sar_height;
if (2 == sscanf(value, "%d:%d", &sar_width, &sar_height)) {
cfg->vui.sar_width = sar_width;
cfg->vui.sar_height = sar_height;
} else
error = 1;
}
else if OPT("overscan")
error = !parse_enum(value, overscan_names, &cfg->vui.overscan);
else if OPT("videoformat")
error = !parse_enum(value, videoformat_names, &cfg->vui.videoformat);
else if OPT("range")
error = !parse_enum(value, range_names, &cfg->vui.fullrange);
else if OPT("colorprim")
error = !parse_enum(value, colorprim_names, &cfg->vui.colorprim);
else if OPT("transfer")
error = !parse_enum(value, transfer_names, &cfg->vui.transfer);
else if OPT("colormatrix")
error = !parse_enum(value, colormatrix_names, &cfg->vui.colormatrix);
else if OPT("chromaloc") {
cfg->vui.chroma_loc = atoi(value);
if (cfg->vui.chroma_loc < 0 || cfg->vui.chroma_loc > 5) {
fprintf(stderr, "--chromaloc parameter out of range [0..5], set to 0\n");
cfg->vui.chroma_loc = 0;
}
}
else if OPT("aud")
cfg->aud_enable = atobool(value);
else if OPT("cqmfile")
cfg->cqmfile = copy_string(value);
else if OPT("seek")
cfg->seek = atoi(value);
else if OPT("tiles-width-split")
error = !parse_tiles_specification(value, &cfg->tiles_width_count, &cfg->tiles_width_split);
else if OPT("tiles-height-split")
error = !parse_tiles_specification(value, &cfg->tiles_height_count, &cfg->tiles_height_split);
else if OPT("wpp")
cfg->wpp = atobool(value);
else if OPT("owf") {
cfg->owf = atoi(value);
if (cfg->owf < 0) {
fprintf(stderr, "--owf parameter smaller than 0, set to 0\n");
cfg->owf = 0;
} else if (cfg->owf == 0 && !strcmp(value, "auto")) {
cfg->owf = -1;
}
}
else if OPT("slice-addresses")
error = !parse_slice_specification(value, &cfg->slice_count, &cfg->slice_addresses_in_ts);
else if OPT("threads")
cfg->threads = atoi(value);
else if OPT("cpuid")
cfg->cpuid = atoi(value);
else if OPT("pu-depth-inter")
{
if (2 == sscanf(value, "%d-%d", &cfg->pu_depth_inter.min, &cfg->pu_depth_inter.max)) {
if (!WITHIN(cfg->pu_depth_inter.min, PU_DEPTH_INTER_MIN, PU_DEPTH_INTER_MAX) ||
!WITHIN(cfg->pu_depth_inter.max, PU_DEPTH_INTER_MIN, PU_DEPTH_INTER_MAX))
{
fprintf(stderr, "Input error: illegal value for --pu-depth-inter (%d-%d)",
cfg->pu_depth_inter.min, cfg->pu_depth_inter.max);
return 0;
}
if (cfg->pu_depth_inter.min > cfg->pu_depth_inter.max) {
fprintf(stderr, "Input error: Inter PU depth min (%d) > max (%d)\n",
cfg->pu_depth_inter.min, cfg->pu_depth_inter.max);
return 0;
}
}
}
else if OPT("pu-depth-intra")
{
if (2 == sscanf(value, "%d-%d", &cfg->pu_depth_intra.min, &cfg->pu_depth_intra.max)) {
if (!WITHIN(cfg->pu_depth_intra.min, PU_DEPTH_INTRA_MIN, PU_DEPTH_INTRA_MAX) ||
!WITHIN(cfg->pu_depth_intra.max, PU_DEPTH_INTRA_MIN, PU_DEPTH_INTRA_MAX))
{
fprintf(stderr, "Input error: illegal value for --pu-depth-intra (%d-%d)",
cfg->pu_depth_intra.min, cfg->pu_depth_intra.max);
return 0;
}
if (cfg->pu_depth_intra.min > cfg->pu_depth_intra.max) {
fprintf(stderr, "Input error: Intra PU depth min (%d) > max (%d)\n",
cfg->pu_depth_intra.min, cfg->pu_depth_intra.max);
return 0;
}
}
}
else
return 0;
#undef OPT
return error ? 0 : 1;
}
/**
* \brief Read configuration options from argv to the config struct
* \param cfg config object
* \param argc argument count
* \param argv argument list
* \return 1 on success, 0 on failure
*/
int config_read(config_t *cfg,int argc, char *argv[])
{
static char short_options[] = "i:o:d:w:h:n:q:p:r:";
static struct option long_options[] =
{
{ "input", required_argument, NULL, 'i' },
{ "output", required_argument, NULL, 'o' },
{ "debug", required_argument, NULL, 'd' },
{ "width", required_argument, NULL, 'w' },
{ "height", required_argument, NULL, 'h' }, // deprecated
{ "frames", required_argument, NULL, 'n' }, // deprecated
{ "qp", required_argument, NULL, 'q' },
{ "period", required_argument, NULL, 'p' },
{ "ref", required_argument, NULL, 'r' },
{ "vps-period", required_argument, NULL, 0 },
{ "input-res", required_argument, NULL, 0 },
{ "no-deblock", no_argument, NULL, 0 },
{ "deblock", required_argument, NULL, 0 },
{ "no-sao", no_argument, NULL, 0 },
{ "no-rdoq", no_argument, NULL, 0 },
{ "no-signhide", no_argument, NULL, 0 },
{ "rd", required_argument, NULL, 0 },
{ "full-intra-search", no_argument, NULL, 0 },
{ "no-transform-skip", no_argument, NULL, 0 },
{ "tr-depth-intra", required_argument, NULL, 0 },
{ "subme", required_argument, NULL, 0 },
{ "sar", required_argument, NULL, 0 },
{ "overscan", required_argument, NULL, 0 },
{ "videoformat", required_argument, NULL, 0 },
{ "range", required_argument, NULL, 0 },
{ "colorprim", required_argument, NULL, 0 },
{ "transfer", required_argument, NULL, 0 },
{ "colormatrix", required_argument, NULL, 0 },
{ "chromaloc", required_argument, NULL, 0 },
{ "aud", no_argument, NULL, 0 },
{ "cqmfile", required_argument, NULL, 0 },
{ "seek", required_argument, NULL, 0 },
{ "tiles-width-split", required_argument, NULL, 0 },
{ "tiles-height-split", required_argument, NULL, 0 },
{ "wpp", no_argument, NULL, 0 },
{ "owf", required_argument, NULL, 0 },
{ "slice-addresses", required_argument, NULL, 0 },
{ "threads", required_argument, NULL, 0 },
{ "cpuid", required_argument, NULL, 0 },
{ "pu-depth-inter", required_argument, NULL, 0 },
{ "pu-depth-intra", required_argument, NULL, 0 },
{0, 0, 0, 0}
};
// Parse command line options
for (optind = 0;;) {
int long_options_index = -1;
int c = getopt_long(argc, argv, short_options, long_options, &long_options_index);
if (c == -1)
break;
if (long_options_index < 0) {
int i;
for (i = 0; long_options[i].name; i++)
if (long_options[i].val == c) {
long_options_index = i;
break;
}
if (long_options_index < 0) {
// getopt_long already printed an error message
return 0;
}
}
if (!config_parse(cfg, long_options[long_options_index].name, optarg)) {
const char *name = long_options_index > 0 ? long_options[long_options_index].name : argv[optind-2];
fprintf(stderr, "invalid argument: %s = %s\r\n", name, optarg );
return 0;
}
}
// Check that the required files were defined
if(cfg->input == NULL || cfg->output == NULL) return 0;
return 1;
}
/**
* \brief A function that does additional checks after config_init.
*
* Add checks that don't make sense to have in config_init here.
* This should be called when cfg is in it's final state.
*
* \return 0 if config fails, otherwise 1.
*/
int config_validate(config_t *cfg)
{
if (cfg->width == 0 || cfg->height == 0) {
fprintf(stderr, "Input error: one of the dimensions is 0: dims=%dx%d", cfg->width, cfg->height);
return 0;
}
//Tile separation should be at round position in terms of LCU, should be monotonic, and should not start by 0
if (cfg->tiles_width_split) {
int i;
int32_t prev_tile_split = 0;
for (i=0; i < cfg->tiles_width_count; ++i) {
if (cfg->tiles_width_split[i] <= prev_tile_split) {
fprintf(stderr, "Input error: tile separations in width should be strictly monotonic (%d <= %d)\n", cfg->tiles_width_split[i], prev_tile_split);
return 0;
}
if ((cfg->tiles_width_split[i] % LCU_WIDTH) != 0) {
fprintf(stderr, "Input error: tile separation in width %d (at %d) is not at a multiple of LCU_WIDTH (%d)\n", i, cfg->tiles_width_split[i], LCU_WIDTH);
return 0;
}
prev_tile_split = cfg->tiles_width_split[i];
}
if (cfg->tiles_width_split[cfg->tiles_width_count-1] >= cfg->width) {
fprintf(stderr, "Input error: last x tile separation in width (%d) should smaller than image width (%d)\n", cfg->tiles_width_split[cfg->tiles_width_count-1], cfg->width);
return 0;
}
}
if (cfg->tiles_height_split) {
int i;
int32_t prev_tile_split = 0;
for (i=0; i < cfg->tiles_height_count; ++i) {
if (cfg->tiles_height_split[i] <= prev_tile_split) {
fprintf(stderr, "Input error: tile separations in height should be strictly monotonic (%d <= %d)\n", cfg->tiles_height_split[i], prev_tile_split);
return 0;
}
if ((cfg->tiles_height_split[i] % LCU_WIDTH) != 0) {
fprintf(stderr, "Input error: tile separation in height %d (at %d) is not at a multiple of LCU_WIDTH (%d)\n", i, cfg->tiles_height_split[i], LCU_WIDTH);
return 0;
}
prev_tile_split = cfg->tiles_height_split[i];
}
if (cfg->tiles_height_split[cfg->tiles_height_count-1] >= cfg->height) {
fprintf(stderr, "Input error: last tile separation in height (%d) should smaller than image height (%d)\n", cfg->tiles_height_split[cfg->tiles_height_count-1], cfg->height);
return 0;
}
}
return 1;
}
int size_of_wpp_ends(int threads)
{
// Based on the shape of the area where all threads can't yet run in parallel.
return 4 * threads * threads - 2 * threads;
}
int config_set_owf_auto(config_t *cfg)
{
if (cfg->wpp) {
// If wpp is on, select owf such that less than 15% of the
// frame is covered by the are threads can not work at the same time.
const int lcu_width = CEILDIV(cfg->width, LCU_WIDTH);
const int lcu_height = CEILDIV(cfg->height, LCU_WIDTH);
// Find the largest number of threads per frame that satifies the
// the condition: wpp start/stop inefficiency takes up less than 15%
// of frame area.
int threads_per_frame = 1;
const int wpp_treshold = lcu_width * lcu_height * 15 / 100;
while ((threads_per_frame + 1) * 2 < lcu_width &&
threads_per_frame + 1 < lcu_height &&
size_of_wpp_ends(threads_per_frame + 1) < wpp_treshold)
{
++threads_per_frame;
}
const int threads = (cfg->threads > 1 ? cfg->threads : 1);
const int frames = CEILDIV(threads, threads_per_frame);
// Convert from number of parallel frames to number of additional frames.
cfg->owf = CLIP(0, threads - 1, frames - 1);
} else {
// If wpp is not on, select owf such that there is enough
// tiles for twice the number of threads.
int tiles_per_frame = 1;
if (cfg->tiles_width_count > 0) {
tiles_per_frame *= cfg->tiles_width_count + 1;
}
if (cfg->tiles_height_count > 0) {
tiles_per_frame *= cfg->tiles_height_count + 1;
}
int threads = (cfg->threads > 1 ? cfg->threads : 1);
int frames = CEILDIV(threads * 4, tiles_per_frame);
// Limit number of frames to 1.25x the number of threads for the case
// where there is only 1 tile per frame.
frames = CLIP(1, threads * 4 / 3, frames);
cfg->owf = frames - 1;
}
fprintf(stderr, "--owf=auto value set to %d.\n", cfg->owf);
return 1;
}