mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-23 18:14:06 +00:00
2081 lines
61 KiB
C
2081 lines
61 KiB
C
/*****************************************************************************
|
|
* This file is part of uvg266 VVC encoder.
|
|
*
|
|
* Copyright (c) 2021, Tampere University, ITU/ISO/IEC, project contributors
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright notice, this
|
|
* list of conditions and the following disclaimer in the documentation and/or
|
|
* other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of the Tampere University or ITU/ISO/IEC nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
* INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
****************************************************************************/
|
|
|
|
#include "cfg.h"
|
|
#include "gop.h"
|
|
#include "alf.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
|
|
static void parse_qp_map(uvg_config* cfg, int index);
|
|
|
|
uvg_config *uvg_config_alloc(void)
|
|
{
|
|
return calloc(1, sizeof(uvg_config));
|
|
}
|
|
|
|
int uvg_config_init(uvg_config *cfg)
|
|
{
|
|
cfg->width = 0;
|
|
cfg->height = 0;
|
|
cfg->framerate_num = 25;
|
|
cfg->framerate_denom = 1;
|
|
cfg->qp = 22;
|
|
cfg->intra_qp_offset = 0;
|
|
cfg->intra_qp_offset_auto = true;
|
|
cfg->intra_period = 64;
|
|
cfg->vps_period = 0;
|
|
cfg->lmcs_enable = 0;
|
|
cfg->deblock_enable = 0;
|
|
cfg->deblock_beta = 0;
|
|
cfg->deblock_tc = 0;
|
|
cfg->sao_type = 3;
|
|
cfg->alf_type = 0;
|
|
cfg->alf_info_in_ph_flag = 0;
|
|
cfg->alf_non_linear_luma = 1;
|
|
cfg->alf_non_linear_chroma = 1;
|
|
cfg->alf_allow_predefined_filters = 1;
|
|
cfg->rdoq_enable = 1;
|
|
cfg->rdoq_skip = 1;
|
|
cfg->signhide_enable = true;
|
|
cfg->rdo = 1;
|
|
cfg->mv_rdo = 0;
|
|
cfg->full_intra_search = 0;
|
|
cfg->trskip_enable = 0;
|
|
cfg->chroma_trskip_enable = 0;
|
|
cfg->trskip_max_size = 2; //Default to 4x4
|
|
cfg->mts = 0;
|
|
cfg->mts_implicit = 0;
|
|
cfg->ime_algorithm = 0; /* hexbs */
|
|
cfg->fme_level = 4;
|
|
cfg->source_scan_type = 0; /* progressive */
|
|
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->fast_coeff_table_fn = NULL;
|
|
cfg->ref_frames = 1;
|
|
cfg->gop_len = 4;
|
|
cfg->gop_lowdelay = true;
|
|
cfg->bipred = 0;
|
|
cfg->target_bitrate = 0;
|
|
cfg->hash = UVG_HASH_CHECKSUM;
|
|
cfg->lossless = false;
|
|
cfg->tmvp_enable = true;
|
|
cfg->implicit_rdpcm = false;
|
|
cfg->fast_residual_cost_limit = 0;
|
|
|
|
cfg->cu_split_termination = UVG_CU_SPLIT_TERMINATION_ZERO;
|
|
|
|
cfg->tiles_width_count = 1;
|
|
cfg->tiles_height_count = 1;
|
|
cfg->tiles_width_split = NULL;
|
|
cfg->tiles_height_split = NULL;
|
|
|
|
cfg->wpp = 1;
|
|
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 = -1;
|
|
cfg->cpuid = 1;
|
|
|
|
// Defaults for what sizes of PUs are tried.
|
|
memset( cfg->pu_depth_inter.min, -1, sizeof( cfg->pu_depth_inter.min ) );
|
|
memset( cfg->pu_depth_inter.max, -1, sizeof( cfg->pu_depth_inter.max ) );
|
|
memset( cfg->pu_depth_intra.min, -1, sizeof( cfg->pu_depth_intra.min ) );
|
|
memset( cfg->pu_depth_intra.max, -1, sizeof( cfg->pu_depth_intra.max ) );
|
|
*cfg->pu_depth_inter.min = 2; // 0-3
|
|
*cfg->pu_depth_inter.max = 3; // 0-3
|
|
*cfg->pu_depth_intra.min = 2; // 0-4
|
|
*cfg->pu_depth_intra.max = 3; // 0-4
|
|
|
|
cfg->add_encoder_info = true;
|
|
cfg->calc_psnr = true;
|
|
|
|
cfg->mv_constraint = UVG_MV_CONSTRAIN_NONE;
|
|
|
|
cfg->me_early_termination = 1;
|
|
cfg->intra_rdo_et = 0;
|
|
|
|
cfg->input_format = UVG_FORMAT_P420;
|
|
cfg->input_bitdepth = 8;
|
|
|
|
cfg->gop_lp_definition.d = 3;
|
|
cfg->gop_lp_definition.t = 1;
|
|
cfg->open_gop = true;
|
|
|
|
cfg->roi.file_path = NULL;
|
|
cfg->roi.format = UVG_ROI_TXT;
|
|
|
|
cfg->set_qp_in_cu = false;
|
|
|
|
cfg->erp_aqp = false;
|
|
|
|
cfg->slices = UVG_SLICES_NONE;
|
|
|
|
cfg->level = 62; // default hevc level, 6.2 (the highest)
|
|
cfg->force_level = true; // don't care about level limits by-default
|
|
cfg->high_tier = false;
|
|
|
|
cfg->me_max_steps = (uint32_t)-1;
|
|
|
|
cfg->vaq = 0;
|
|
|
|
cfg->scaling_list = UVG_SCALING_LIST_OFF;
|
|
|
|
cfg->max_merge = 6;
|
|
cfg->early_skip = true;
|
|
|
|
cfg->intra_smoothing_disabled = false;
|
|
cfg->ml_pu_depth_intra = false;
|
|
|
|
cfg->partial_coding.startCTU_x = 0;
|
|
cfg->partial_coding.startCTU_y = 0;
|
|
cfg->partial_coding.fullWidth = 0;
|
|
cfg->partial_coding.fullHeight = 0;
|
|
|
|
cfg->zero_coeff_rdo = true;
|
|
|
|
cfg->rc_algorithm = UVG_NO_RC;
|
|
cfg->intra_bit_allocation = false;
|
|
cfg->clip_neighbour = true;
|
|
|
|
cfg->file_format = UVG_FORMAT_AUTO;
|
|
|
|
cfg->stats_file_prefix = NULL;
|
|
|
|
cfg->log2_parallel_merge_level = 2;
|
|
cfg->fastrd_sampling_on = 0;
|
|
cfg->fastrd_accuracy_check_on = 0;
|
|
cfg->fastrd_learning_outdir_fn = NULL;
|
|
|
|
cfg->chroma_scale_out[0][0] = cfg->chroma_scale_in[0][0] = 17;
|
|
cfg->chroma_scale_out[0][1] = cfg->chroma_scale_in[0][1] = 27;
|
|
cfg->chroma_scale_out[0][2] = cfg->chroma_scale_in[0][2] = 32;
|
|
cfg->chroma_scale_out[0][3] = cfg->chroma_scale_in[0][3] = 44;
|
|
cfg->chroma_scale_out[0][4] = cfg->chroma_scale_in[0][4] = -1;
|
|
cfg->chroma_scale_out[1][0] = cfg->chroma_scale_in[1][0] = -1;
|
|
cfg->chroma_scale_out[2][0] = cfg->chroma_scale_in[2][0] = -1;
|
|
|
|
cfg->mrl = false;
|
|
|
|
cfg->mip = false;
|
|
|
|
cfg->lfnst = false;
|
|
|
|
cfg->isp = false;
|
|
|
|
parse_qp_map(cfg, 0);
|
|
|
|
cfg->jccr = 0;
|
|
|
|
cfg->amvr = 0;
|
|
|
|
cfg->cclm = 0;
|
|
|
|
cfg->combine_intra_cus = 1;
|
|
cfg->force_inter = 0;
|
|
|
|
cfg->cabac_debug_file_name = NULL;
|
|
|
|
cfg->dual_tree = 0;
|
|
|
|
cfg->min_qt_size[0] = 4;
|
|
cfg->min_qt_size[1] = 4;
|
|
cfg->min_qt_size[2] = 4;
|
|
|
|
cfg->max_btt_depth[0] = 0;
|
|
cfg->max_btt_depth[1] = 0;
|
|
cfg->max_btt_depth[2] = 0;
|
|
|
|
cfg->max_tt_size[0] = 64;
|
|
cfg->max_bt_size[0] = 64;
|
|
cfg->max_tt_size[1] = 64;
|
|
cfg->max_bt_size[1] = 64;
|
|
cfg->max_tt_size[2] = 64;
|
|
cfg->max_bt_size[2] = 64;
|
|
|
|
cfg->intra_rough_search_levels = 2;
|
|
|
|
cfg->ibc = 0;
|
|
|
|
cfg->dep_quant = 0;
|
|
return 1;
|
|
}
|
|
|
|
int uvg_config_destroy(uvg_config *cfg)
|
|
{
|
|
if (cfg) {
|
|
FREE_POINTER(cfg->cqmfile);
|
|
FREE_POINTER(cfg->roi.file_path);
|
|
FREE_POINTER(cfg->fast_coeff_table_fn);
|
|
FREE_POINTER(cfg->tiles_width_split);
|
|
FREE_POINTER(cfg->tiles_height_split);
|
|
FREE_POINTER(cfg->slice_addresses_in_ts);
|
|
FREE_POINTER(cfg->fastrd_learning_outdir_fn);
|
|
}
|
|
free(cfg);
|
|
|
|
return 1;
|
|
}
|
|
|
|
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_n(const char *arg, unsigned num_chars, const char * const *names, int8_t *dst)
|
|
{
|
|
int8_t i;
|
|
for (i = 0; names[i]; i++) {
|
|
if (!strncmp(arg, names[i], num_chars)) {
|
|
*dst = i;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_enum(const char *arg, const char * const *names, int8_t *dst)
|
|
{
|
|
return parse_enum_n(arg, 255, names, dst);
|
|
}
|
|
|
|
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);
|
|
if (MAX_TILES_PER_DIM <= *ntiles || 1 >= *ntiles) {
|
|
fprintf(stderr, "Invalid number of tiles (1 <= %d <= %d = MAX_TILES_PER_DIM)!\n", *ntiles, 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 = 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[*ntiles - 1] = current_value;
|
|
++(*ntiles);
|
|
if (MAX_TILES_PER_DIM <= *ntiles) break;
|
|
} while (current_arg);
|
|
|
|
if (MAX_TILES_PER_DIM <= *ntiles || 1 >= *ntiles) {
|
|
fprintf(stderr, "Invalid number of tiles (1 <= %d <= %d = MAX_TILES_PER_DIM)!\n", *ntiles, MAX_TILES_PER_DIM);
|
|
return 0;
|
|
}
|
|
|
|
*array = MALLOC(int32_t, *ntiles - 1);
|
|
if (!*array) {
|
|
fprintf(stderr, "Could not allocate array for tiles\n");
|
|
return 0;
|
|
}
|
|
|
|
//TODO: memcpy?
|
|
for (i = 0; i < *ntiles - 1; ++i) {
|
|
(*array)[i] = values[i];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int parse_uint8(const char *numstr,uint8_t* number,int min, int max)
|
|
{
|
|
char *tail;
|
|
int d = strtol(numstr, &tail, 10);
|
|
if (*tail || d < min || d > max){
|
|
fprintf(stderr, "Expected number between %d and %d\n", min, max);
|
|
if(number)
|
|
*number = 0;
|
|
return 0;
|
|
} else{
|
|
if (number)
|
|
*number = (uint8_t) d;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int parse_int8(const char *numstr,int8_t* number,int min, int max)
|
|
{
|
|
char *tail;
|
|
int d = strtol(numstr, &tail, 10);
|
|
if (*tail || d < min || d > max){
|
|
fprintf(stderr, "Expected number between %d and %d\n", min, max);
|
|
if(number)
|
|
*number = 0;
|
|
return 0;
|
|
} else{
|
|
if (number)
|
|
*number = (int8_t) d;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int parse_array(const char *array, uint8_t *coeff_key, int size,
|
|
int min, int max)
|
|
{
|
|
char *key = strdup(array);
|
|
const char delim[] = ",;:";
|
|
char *token;
|
|
int i = 0;
|
|
|
|
token = strtok(key, delim);
|
|
while(token!=NULL&&i<size){
|
|
if (!parse_uint8(token, &coeff_key[i], min, max))
|
|
{
|
|
free(key);
|
|
return 0;
|
|
}
|
|
i++;
|
|
token = strtok(NULL, delim);
|
|
}
|
|
if(i>=size && (token != NULL)){
|
|
fprintf(stderr, "parsing failed : too many members.\n");
|
|
free(key);
|
|
return 0;
|
|
}
|
|
//else if (i<size){
|
|
// fprintf(stderr, "parsing failed : too few members.\n");
|
|
// free(key);
|
|
// return 0;
|
|
//}
|
|
free(key);
|
|
return i;
|
|
}
|
|
|
|
|
|
static int parse_qp_scale_array(const char *array, int8_t *out)
|
|
{
|
|
const int size = 16;
|
|
char *key = strdup(array);
|
|
const char delim[] = ",;:";
|
|
char *token;
|
|
int i = 0;
|
|
|
|
token = strtok(key, delim);
|
|
while(token!=NULL&&i<size){
|
|
if (!parse_int8(token, &out[i], 0, 63))
|
|
{
|
|
free(key);
|
|
return 0;
|
|
}
|
|
i++;
|
|
token = strtok(NULL, delim);
|
|
}
|
|
|
|
if (i>=size){
|
|
fprintf(stderr, "parsing failed : too many members.\n");
|
|
free(key);
|
|
return 0;
|
|
}
|
|
out[i] = -1;
|
|
|
|
free(key);
|
|
return 1;
|
|
}
|
|
|
|
static void parse_qp_map(uvg_config *cfg, int index) {
|
|
int i = 0;
|
|
for (; cfg->chroma_scale_in[index][i] != -1; i++);
|
|
if (cfg->chroma_scale_out[index][i] != -1) return;
|
|
assert(i < 17);
|
|
|
|
// TODO: Move this to somewhere else when we have more than one table
|
|
cfg->num_used_table = 1;
|
|
cfg->qp_table_length_minus1[index] = i - 2;
|
|
cfg->qp_table_start_minus26[index] = cfg->chroma_scale_in[index][0] - 26;
|
|
for (i = 0; i < cfg->qp_table_length_minus1[0] + 1; i++) {
|
|
cfg->delta_qp_in_val_minus1[index][i] = cfg->chroma_scale_in[index][i + 1] - cfg->chroma_scale_in[index][i] - (int8_t)1;
|
|
cfg->delta_qp_out_val[index][i] = cfg->chroma_scale_out[index][i + 1] - cfg->chroma_scale_out[index][i];
|
|
}
|
|
}
|
|
|
|
static int parse_pu_depth_list( const char *array, int32_t *depths_min, int32_t *depths_max, int size )
|
|
{
|
|
char *list = strdup( array );
|
|
char *token;
|
|
int i = 0;
|
|
int ptr = -1;
|
|
int len = (int)strlen( list );
|
|
int retval = 1;
|
|
|
|
//Reset depths in case multiple pu depth parameters are given
|
|
if(size > 1) memset( depths_max + 1, -1, (size - 1) * sizeof( *depths_max ) );
|
|
if(size > 1) memset( depths_min + 1, -1, (size - 1) * sizeof( *depths_min ) );
|
|
|
|
token = strtok( list, "," );
|
|
while( ptr < len && list[ptr + 1] == ',' )
|
|
{
|
|
i++;
|
|
ptr++;
|
|
}
|
|
while( retval && token != NULL && i < size ) {
|
|
retval &= (sscanf( token, "%d-%d", &depths_min[i], &depths_max[i] ) == 2);
|
|
ptr += (retval ? 4 : 0);
|
|
i++;
|
|
token = strtok( NULL, "," );
|
|
while(ptr < len && list[ptr + 1] == ',' ){
|
|
i++;
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
if( i >= size && ( token != NULL ) ) {
|
|
fprintf( stderr, "parsing failed : too many values.\n" );
|
|
retval = 0;
|
|
}
|
|
|
|
free( list );
|
|
return retval;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int uvg_config_parse(uvg_config *cfg, const char *name, const char *value)
|
|
{
|
|
static const char * const me_names[] = { "hexbs", "tz", "full", "full8", "full16", "full32", "full64", "dia", NULL };
|
|
static const char * const source_scan_type_names[] = { "progressive", "tff", "bff", NULL };
|
|
|
|
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 };
|
|
static const char * const mv_constraint_names[] = { "none", "frame", "tile", "frametile", "frametilemargin", NULL };
|
|
static const char * const hash_names[] = { "none", "checksum", "md5", NULL };
|
|
|
|
static const char * const cu_split_termination_names[] = { "zero", "off", NULL };
|
|
|
|
static const char * const me_early_termination_names[] = { "off", "on", "sensitive", NULL };
|
|
|
|
static const char * const sao_names[] = { "off", "edge", "band", "full", NULL };
|
|
static const char * const alf_names[] = { "off", "no-cc", "full", NULL };
|
|
|
|
static const char * const mts_names[] = { "off", "intra", "inter", "both", "implicit", NULL };
|
|
|
|
static const char * const scaling_list_names[] = { "off", "custom", "default", NULL };
|
|
|
|
static const char * const rc_algorithm_names[] = { "no-rc", "lambda", "oba", NULL };
|
|
|
|
static const char * const file_format_names[] = {"auto", "y4m", "yuv", NULL};
|
|
|
|
static const char * const preset_values[11][25*2] = {
|
|
{
|
|
"ultrafast",
|
|
"rd", "0",
|
|
"pu-depth-intra", "2-3",
|
|
"pu-depth-inter", "1-2",
|
|
"me", "hexbs",
|
|
"gop", "8",
|
|
"ref", "1",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "0",
|
|
"sao", "off",
|
|
"rdoq", "0",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "sensitive",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "28",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"superfast",
|
|
"rd", "0",
|
|
"pu-depth-intra", "2-3",
|
|
"pu-depth-inter", "1-2",
|
|
"me", "hexbs",
|
|
"gop", "8",
|
|
"ref", "1",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "2",
|
|
"sao", "full",
|
|
"rdoq", "0",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "sensitive",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "28",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"veryfast",
|
|
"rd", "0",
|
|
"pu-depth-intra", "2-3",
|
|
"pu-depth-inter", "1-3",
|
|
"me", "hexbs",
|
|
"gop", "8",
|
|
"ref", "1",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "2",
|
|
"sao", "full",
|
|
"rdoq", "0",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "sensitive",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "28",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"faster",
|
|
"rd", "0",
|
|
"pu-depth-intra", "2-3",
|
|
"pu-depth-inter", "1-3",
|
|
"me", "hexbs",
|
|
"gop", "8",
|
|
"ref", "1",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "0",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "sensitive",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"fast",
|
|
"rd", "0",
|
|
"pu-depth-intra", "1-3",
|
|
"pu-depth-inter", "1-3",
|
|
"me", "hexbs",
|
|
"gop", "8",
|
|
"ref", "2",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "0",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "sensitive",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"medium",
|
|
"rd", "0",
|
|
"pu-depth-intra", "1-4",
|
|
"pu-depth-inter", "0-3",
|
|
"me", "hexbs",
|
|
"gop", "16",
|
|
"ref", "4",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "1",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "on",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"slow",
|
|
"rd", "1",
|
|
"pu-depth-intra", "1-4",
|
|
"pu-depth-inter", "0-3",
|
|
"me", "hexbs",
|
|
"gop", "16",
|
|
"ref", "4",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "0",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "1",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "on",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"slower",
|
|
"rd", "2",
|
|
"pu-depth-intra", "1-4",
|
|
"pu-depth-inter", "0-3",
|
|
"me", "hexbs",
|
|
"gop", "16",
|
|
"ref", "4",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "1",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "1",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "0",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "off",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"veryslow",
|
|
"rd", "2",
|
|
"pu-depth-intra", "1-4",
|
|
"pu-depth-inter", "0-3",
|
|
"me", "tz",
|
|
"gop", "16",
|
|
"ref", "4",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "1",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "1",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "1",
|
|
"mv-rdo", "0",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "zero",
|
|
"me-early-termination", "off",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "1",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{
|
|
"placebo",
|
|
"rd", "2",
|
|
"pu-depth-intra", "1-4",
|
|
"pu-depth-inter", "0-3",
|
|
"me", "tz",
|
|
"gop", "16",
|
|
"ref", "4",
|
|
"bipred", "1",
|
|
"deblock", "0:0",
|
|
"signhide", "1",
|
|
"subme", "4",
|
|
"sao", "full",
|
|
"rdoq", "1",
|
|
"rdoq-skip", "0",
|
|
"transform-skip", "1",
|
|
"mv-rdo", "1",
|
|
"full-intra-search", "0",
|
|
"cu-split-termination", "off",
|
|
"me-early-termination", "off",
|
|
"intra-rdo-et", "0",
|
|
"early-skip", "0",
|
|
"fast-residual-cost", "0",
|
|
"max-merge", "6",
|
|
NULL
|
|
},
|
|
{ NULL }
|
|
};
|
|
|
|
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-res")
|
|
if (!strcmp(value, "auto")) {
|
|
return 1;
|
|
} else {
|
|
return (sscanf(value, "%dx%d", &cfg->width, &cfg->height) == 2);
|
|
}
|
|
else if OPT("input-fps") {
|
|
int32_t fps_num, fps_denom;
|
|
if (sscanf(value, "%d/%d", &fps_num, &fps_denom) == 2) {
|
|
cfg->framerate_num = fps_num;
|
|
cfg->framerate_denom = fps_denom;
|
|
} else {
|
|
// Accept decimal notation, making sure not to round 0 to 1.
|
|
cfg->framerate_num = (int)(atof(value) * 1000 + 0.49);
|
|
cfg->framerate_denom = 1000;
|
|
}
|
|
}
|
|
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);
|
|
else if OPT("lmcs") {
|
|
cfg->lmcs_enable = atobool(value);
|
|
}
|
|
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 {
|
|
cfg->deblock_enable = atobool(value);
|
|
}
|
|
}
|
|
else if OPT("sao") {
|
|
int8_t sao_type = 0;
|
|
if (!parse_enum(value, sao_names, &sao_type)) sao_type = atobool(value) ? 3 : 0;
|
|
cfg->sao_type = sao_type;
|
|
}
|
|
else if OPT("alf") {
|
|
int8_t alf_type = 0;
|
|
if (!parse_enum(value, alf_names, &alf_type)) alf_type = atobool(value) ? 2 : 0;
|
|
cfg->alf_type = alf_type;
|
|
}
|
|
else if OPT("rdoq")
|
|
cfg->rdoq_enable = atobool(value);
|
|
else if OPT("signhide")
|
|
cfg->signhide_enable = (bool)atobool(value);
|
|
else if OPT("rd")
|
|
cfg->rdo = atoi(value);
|
|
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("chroma-transform-skip")
|
|
cfg->chroma_trskip_enable = atobool(value);
|
|
else if OPT("tr-skip-max-size") {
|
|
cfg->trskip_max_size = atoi(value);
|
|
if (cfg->trskip_max_size < 2 || cfg->trskip_max_size > 5) {
|
|
fprintf(stderr, "tr-skip-max-size not between 2 and 5.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("mts") {
|
|
int8_t mts_type = 0;
|
|
if (!parse_enum(value, mts_names, &mts_type)) mts_type = atobool(value) ? 3 : 0;
|
|
cfg->mts = mts_type;
|
|
cfg->mts_implicit = (mts_type == UVG_MTS_IMPLICIT);
|
|
}
|
|
else if OPT("me") {
|
|
int8_t ime_algorithm = 0;
|
|
if (!parse_enum(value, me_names, &ime_algorithm)) return 0;
|
|
cfg->ime_algorithm = ime_algorithm;
|
|
}
|
|
else if OPT("subme")
|
|
cfg->fme_level = atoi(value);
|
|
else if OPT("source-scan-type")
|
|
return parse_enum(value, source_scan_type_names, &cfg->source_scan_type);
|
|
else if OPT("mv-constraint")
|
|
{
|
|
int8_t constraint = UVG_MV_CONSTRAIN_NONE;
|
|
int result = parse_enum(value, mv_constraint_names, &constraint);
|
|
cfg->mv_constraint = constraint;
|
|
return result;
|
|
}
|
|
else if OPT("sar")
|
|
return sscanf(value, "%d:%d", &cfg->vui.sar_width, &cfg->vui.sar_height) == 2;
|
|
else if OPT("overscan")
|
|
return parse_enum(value, overscan_names, &cfg->vui.overscan);
|
|
else if OPT("videoformat")
|
|
return parse_enum(value, videoformat_names, &cfg->vui.videoformat);
|
|
else if OPT("range")
|
|
return parse_enum(value, range_names, &cfg->vui.fullrange);
|
|
else if OPT("colorprim")
|
|
return parse_enum(value, colorprim_names, &cfg->vui.colorprim);
|
|
else if OPT("transfer")
|
|
return parse_enum(value, transfer_names, &cfg->vui.transfer);
|
|
else if OPT("colormatrix")
|
|
return parse_enum(value, colormatrix_names, &cfg->vui.colormatrix);
|
|
else if OPT("chromaloc")
|
|
cfg->vui.chroma_loc = atoi(value);
|
|
else if OPT("aud")
|
|
cfg->aud_enable = atobool(value);
|
|
else if OPT("cqmfile") {
|
|
char* cqmfile = strdup(value);
|
|
if (!cqmfile) {
|
|
fprintf(stderr, "Failed to allocate memory for CQM file name.\n");
|
|
return 0;
|
|
}
|
|
FREE_POINTER(cfg->cqmfile);
|
|
cfg->cqmfile = cqmfile;
|
|
cfg->scaling_list = UVG_SCALING_LIST_CUSTOM;
|
|
}
|
|
else if OPT("fast-coeff-table") {
|
|
char* fast_coeff_table_fn = strdup(value);
|
|
if (!fast_coeff_table_fn) {
|
|
fprintf(stderr, "Failed to allocate memory for fast coeff table file name.\n");
|
|
return 0;
|
|
}
|
|
FREE_POINTER(cfg->fast_coeff_table_fn);
|
|
cfg->fast_coeff_table_fn = fast_coeff_table_fn;
|
|
}
|
|
else if OPT("fastrd-sampling") {
|
|
cfg->fastrd_sampling_on = 1;
|
|
}
|
|
else if OPT("fastrd-accuracy-check") {
|
|
cfg->fastrd_accuracy_check_on = 1;
|
|
}
|
|
else if OPT("fastrd-outdir") {
|
|
char *fastrd_learning_outdir_fn = strdup(value);
|
|
if (!fastrd_learning_outdir_fn) {
|
|
fprintf(stderr, "Failed to allocate memory for fast RD learning outfile name.\n");
|
|
return 0;
|
|
}
|
|
FREE_POINTER(cfg->fastrd_learning_outdir_fn);
|
|
cfg->fastrd_learning_outdir_fn = fastrd_learning_outdir_fn;
|
|
}
|
|
else if OPT("scaling-list") {
|
|
int8_t scaling_list = UVG_SCALING_LIST_OFF;
|
|
int result = parse_enum(value, scaling_list_names, &scaling_list);
|
|
cfg->scaling_list = scaling_list;
|
|
return result;
|
|
}
|
|
else if OPT("tiles-width-split") {
|
|
int retval = parse_tiles_specification(value, &cfg->tiles_width_count, &cfg->tiles_width_split);
|
|
|
|
if (cfg->tiles_width_count > 1 && cfg->tmvp_enable) {
|
|
cfg->tmvp_enable = false;
|
|
fprintf(stderr, "Disabling TMVP because tiles are used.\n");
|
|
}
|
|
|
|
if (cfg->wpp) {
|
|
cfg->wpp = false;
|
|
fprintf(stderr, "Disabling WPP because tiles were enabled.\n");
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
else if OPT("tiles-height-split") {
|
|
int retval = parse_tiles_specification(value, &cfg->tiles_height_count, &cfg->tiles_height_split);
|
|
|
|
if (cfg->tiles_height_count > 1 && cfg->tmvp_enable) {
|
|
cfg->tmvp_enable = false;
|
|
fprintf(stderr, "Disabling TMVP because tiles are used.\n");
|
|
}
|
|
|
|
if (cfg->wpp) {
|
|
cfg->wpp = false;
|
|
fprintf(stderr, "Disabling WPP because tiles were enabled.\n");
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
else if OPT("tiles")
|
|
{
|
|
// A simpler interface for setting tiles, accepting only uniform split.
|
|
unsigned width;
|
|
unsigned height;
|
|
if (2 != sscanf(value, "%ux%u", &width, &height)) {
|
|
fprintf(stderr, "Wrong format for tiles. Expected \"%%ux%%u\", but got \"%s\"\n", value);
|
|
return 0;
|
|
}
|
|
|
|
if (MAX_TILES_PER_DIM <= width || 1 > width) {
|
|
fprintf(stderr, "Invalid number of tiles (0 < %d <= %d = MAX_TILES_PER_DIM)!\n", width, MAX_TILES_PER_DIM);
|
|
return 0;
|
|
}
|
|
if (MAX_TILES_PER_DIM <= height || 1 > height) {
|
|
fprintf(stderr, "Invalid number of tiles (0 < %d <= %d = MAX_TILES_PER_DIM)!\n", height, MAX_TILES_PER_DIM);
|
|
return 0;
|
|
}
|
|
|
|
// Free split arrays incase they have already been set by another parameter.
|
|
FREE_POINTER(cfg->tiles_width_split);
|
|
FREE_POINTER(cfg->tiles_height_split);
|
|
cfg->tiles_width_count = width;
|
|
cfg->tiles_height_count = height;
|
|
|
|
if (cfg->tmvp_enable) {
|
|
cfg->tmvp_enable = false;
|
|
fprintf(stderr, "Disabling TMVP because tiles are used.\n");
|
|
}
|
|
|
|
if (cfg->wpp) {
|
|
cfg->wpp = false;
|
|
fprintf(stderr, "Disabling WPP because tiles were enabled.\n");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else if OPT("wpp")
|
|
cfg->wpp = atobool(value);
|
|
else if OPT("owf") {
|
|
cfg->owf = atoi(value);
|
|
if (cfg->owf == 0 && !strcmp(value, "auto")) {
|
|
// -1 means automatic selection
|
|
cfg->owf = -1;
|
|
}
|
|
} else if OPT("slices") {
|
|
if (!strcmp(value, "tiles")) {
|
|
cfg->slices = UVG_SLICES_TILES;
|
|
return 1;
|
|
} else if (!strcmp(value, "wpp")) {
|
|
cfg->slices = UVG_SLICES_WPP;
|
|
return 1;
|
|
} else if (!strcmp(value, "tiles+wpp")) {
|
|
cfg->slices = UVG_SLICES_TILES | UVG_SLICES_WPP;
|
|
return 1;
|
|
} else {
|
|
return parse_slice_specification(value, &cfg->slice_count, &cfg->slice_addresses_in_ts);
|
|
}
|
|
|
|
} else if OPT("threads") {
|
|
cfg->threads = atoi(value);
|
|
if (cfg->threads == 0 && !strcmp(value, "auto")) {
|
|
// -1 means automatic selection
|
|
cfg->threads = -1;
|
|
}
|
|
}
|
|
else if OPT("cpuid")
|
|
cfg->cpuid = atobool(value);
|
|
else if OPT("pu-depth-inter")
|
|
return parse_pu_depth_list(value, cfg->pu_depth_inter.min, cfg->pu_depth_inter.max, UVG_MAX_GOP_LAYERS);
|
|
else if OPT("pu-depth-intra")
|
|
return parse_pu_depth_list(value, cfg->pu_depth_intra.min, cfg->pu_depth_intra.max, UVG_MAX_GOP_LAYERS);
|
|
else if OPT("info")
|
|
cfg->add_encoder_info = atobool(value);
|
|
else if OPT("gop") {
|
|
if (!strncmp(value, "lp-", 3)) { // Handle GOPs starting with "lp-".
|
|
struct {
|
|
unsigned g; // length
|
|
unsigned d; // depth
|
|
unsigned t; // temporal
|
|
} gop = { 0, 0, 0 };
|
|
|
|
// Parse --gop=lp-g#d#t#
|
|
if (sscanf(value, "lp-g%ud%ut%u", &gop.g, &gop.d, &gop.t) != 3) {
|
|
fprintf(stderr, "Error in GOP syntax. Example: lp-g8d4t2\n");
|
|
return 0;
|
|
}
|
|
|
|
if (gop.g < 1 || gop.g > 32) {
|
|
fprintf(stderr, "gop.g must be between 1 and 32.\n");
|
|
return 0;
|
|
}
|
|
if (gop.d < 1 || gop.d > 8) {
|
|
fprintf(stderr, "gop.d must be between 1 and 8.\n");
|
|
return 0;
|
|
}
|
|
if (gop.t < 1 || gop.t > 15) {
|
|
fprintf(stderr, "gop.t must be between 1 and 15.\n");
|
|
return 0;
|
|
}
|
|
|
|
cfg->gop_lowdelay = true;
|
|
cfg->gop_len = gop.g;
|
|
cfg->gop_lp_definition.d = gop.d;
|
|
cfg->gop_lp_definition.t = gop.t;
|
|
|
|
cfg->intra_bit_allocation = true;
|
|
cfg->clip_neighbour = false;
|
|
} else if (atoi(value) == 8) {
|
|
cfg->gop_lowdelay = 0;
|
|
cfg->gop_len = sizeof(uvg_gop_ra8) / sizeof(uvg_gop_ra8[0]);
|
|
memcpy(cfg->gop, uvg_gop_ra8, sizeof(uvg_gop_ra8));
|
|
cfg->intra_bit_allocation = false;
|
|
cfg->clip_neighbour = true;
|
|
|
|
} else if (atoi(value) == 16) {
|
|
cfg->gop_lowdelay = 0;
|
|
cfg->gop_len = sizeof(uvg_gop_ra16) / sizeof(uvg_gop_ra16[0]);
|
|
memcpy(cfg->gop, uvg_gop_ra16, sizeof(uvg_gop_ra16));
|
|
cfg->intra_bit_allocation = false;
|
|
cfg->clip_neighbour = true;
|
|
|
|
} else if (atoi(value) == 0) {
|
|
//Disable gop
|
|
cfg->gop_len = 0;
|
|
cfg->gop_lowdelay = 0;
|
|
cfg->gop_lp_definition.d = 0;
|
|
cfg->gop_lp_definition.t = 0;
|
|
} else if (atoi(value)) {
|
|
fprintf(stderr, "Input error: unsupported gop length, must be 0 or 8\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("intra-qp-offset") {
|
|
cfg->intra_qp_offset = atoi(value);
|
|
if( cfg->intra_qp_offset == 0 && !strcmp( value, "auto" ) )
|
|
{
|
|
cfg->intra_qp_offset_auto = true;
|
|
} else {
|
|
cfg->intra_qp_offset_auto = false;
|
|
}
|
|
}
|
|
else if OPT("open-gop") {
|
|
cfg->open_gop = (bool)atobool(value);
|
|
}
|
|
else if OPT("bipred")
|
|
cfg->bipred = atobool(value);
|
|
else if OPT("bitrate") {
|
|
cfg->target_bitrate = atoi(value);
|
|
if (!cfg->rc_algorithm) {
|
|
cfg->rc_algorithm = UVG_LAMBDA;
|
|
}
|
|
}
|
|
else if OPT("preset") {
|
|
int preset_line = 0;
|
|
|
|
// Accept numbers from 0 to 9.
|
|
if ((atoi(value) == 0 && !strcmp(value, "0")) || (atoi(value) >= 1 && atoi(value) <= 9)) {
|
|
preset_line = atoi(value);
|
|
} else {
|
|
// Find the selected preset from the list
|
|
while (preset_values[preset_line][0] != NULL) {
|
|
if (!strcmp(value, preset_values[preset_line][0])) {
|
|
break;
|
|
}
|
|
preset_line++;
|
|
}
|
|
}
|
|
|
|
if (preset_values[preset_line][0] != NULL) {
|
|
fprintf(stderr, "Using preset %s: ", value);
|
|
// Loop all the name and value pairs and push to the config parser
|
|
for (int preset_value = 1; preset_values[preset_line][preset_value] != NULL; preset_value += 2) {
|
|
fprintf(stderr, "--%s=%s ", preset_values[preset_line][preset_value], preset_values[preset_line][preset_value + 1]);
|
|
uvg_config_parse(cfg, preset_values[preset_line][preset_value], preset_values[preset_line][preset_value + 1]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
} else {
|
|
fprintf(stderr, "Input error: unknown preset \"%s\"\n", value);
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("mv-rdo")
|
|
cfg->mv_rdo = atobool(value);
|
|
else if OPT("psnr")
|
|
cfg->calc_psnr = (bool)atobool(value);
|
|
else if OPT("hash")
|
|
{
|
|
int8_t hash;
|
|
int result;
|
|
if ((result = parse_enum(value, hash_names, &hash))) {
|
|
cfg->hash = hash;
|
|
}
|
|
return result;
|
|
}
|
|
else if OPT("cu-split-termination")
|
|
{
|
|
int8_t mode = UVG_CU_SPLIT_TERMINATION_ZERO;
|
|
int result = parse_enum(value, cu_split_termination_names, &mode);
|
|
cfg->cu_split_termination = mode;
|
|
return result;
|
|
}
|
|
else if OPT("me-early-termination"){
|
|
int8_t mode = 0;
|
|
int result = parse_enum(value, me_early_termination_names, &mode);
|
|
cfg->me_early_termination = mode;
|
|
return result;
|
|
}
|
|
else if OPT("intra-rdo-et")
|
|
cfg->intra_rdo_et = (bool)atobool(value);
|
|
else if OPT("lossless")
|
|
cfg->lossless = (bool)atobool(value);
|
|
else if OPT("tmvp") {
|
|
cfg->tmvp_enable = atobool(value);
|
|
if (cfg->tiles_width_count > 1 || cfg->tiles_height_count > 1) {
|
|
fprintf(stderr, "Cannot enable TMVP because tiles are used.\n");
|
|
cfg->tmvp_enable = false;
|
|
}
|
|
}
|
|
else if OPT("rdoq-skip"){
|
|
cfg->rdoq_skip = atobool(value);
|
|
}
|
|
else if OPT("input-format") {
|
|
static enum uvg_input_format const formats[] = { UVG_FORMAT_P400, UVG_FORMAT_P420 };
|
|
static const char * const format_names[] = { "P400", "P420", NULL };
|
|
|
|
int8_t format = 0;
|
|
if (!parse_enum(value, format_names, &format)) {
|
|
fprintf(stderr, "input-format not recognized.\n");
|
|
return 0;
|
|
}
|
|
|
|
cfg->input_format = formats[format];
|
|
}
|
|
else if OPT("input-bitdepth") {
|
|
cfg->input_bitdepth = atoi(value);
|
|
if (cfg->input_bitdepth < 8 || cfg->input_bitdepth > 16) {
|
|
fprintf(stderr, "input-bitdepth not between 8 and 16.\n");
|
|
return 0;
|
|
}
|
|
if (cfg->input_bitdepth > 8 && UVG_BIT_DEPTH == 8) {
|
|
// Because the image is read straight into the reference buffers,
|
|
// reading >8 bit samples doesn't work when sizeof(uvg_pixel)==1.
|
|
fprintf(stderr, "input-bitdepth can't be set to larger than 8 because"
|
|
" uvg266 is compiled with UVG_BIT_DEPTH=8.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("implicit-rdpcm")
|
|
cfg->implicit_rdpcm = (bool)atobool(value);
|
|
|
|
else if OPT("roi") {
|
|
static enum uvg_roi_format const formats[] = { UVG_ROI_TXT, UVG_ROI_BIN };
|
|
static const char * const format_names[] = { "txt", "bin", NULL };
|
|
|
|
char *roi_file = strdup(value);
|
|
if (!roi_file) {
|
|
fprintf(stderr, "Failed to allocate memory for ROI file name.\n");
|
|
return 0;
|
|
}
|
|
FREE_POINTER(cfg->roi.file_path);
|
|
cfg->roi.file_path = roi_file;
|
|
|
|
// Get file extension or the substring after the last dot
|
|
char *maybe_extension = strrchr(cfg->roi.file_path, '.');
|
|
if (!maybe_extension) {
|
|
cfg->roi.format = UVG_ROI_TXT;
|
|
} else {
|
|
maybe_extension++;
|
|
int8_t format;
|
|
bool unknown_format = !parse_enum(maybe_extension, format_names, &format);
|
|
cfg->roi.format = unknown_format ? UVG_ROI_TXT : formats[format];
|
|
}
|
|
}
|
|
else if OPT("set-qp-in-cu") {
|
|
cfg->set_qp_in_cu = (bool)atobool(value);
|
|
}
|
|
else if OPT("erp-aqp") {
|
|
cfg->erp_aqp = (bool)atobool(value);
|
|
}
|
|
else if (OPT("level") || OPT("force-level")) {
|
|
if OPT("force-level") {
|
|
cfg->force_level = true;
|
|
} else {
|
|
cfg->force_level = false;
|
|
}
|
|
|
|
unsigned int num_first, num_second, level;
|
|
int matched_amount = sscanf(value, "%u.%u", &num_first, &num_second);
|
|
|
|
if (matched_amount == 2) {
|
|
// of form x.y
|
|
level = num_first * 10 + num_second;
|
|
} else if (matched_amount == 1) {
|
|
// no dot
|
|
if (num_first < 10) {
|
|
// of form x
|
|
level = num_first * 10;
|
|
} else {
|
|
// of form xx
|
|
level = num_first;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Invalid level value: \"%s\"\n", value);
|
|
return 0;
|
|
}
|
|
if (level < 10 || level > 62) {
|
|
fprintf(stderr, "Level value of %s is out of bounds\n", value);
|
|
return 0;
|
|
}
|
|
|
|
cfg->level = level;
|
|
}
|
|
else if (OPT("high-tier")) {
|
|
cfg->high_tier = true;
|
|
}
|
|
else if (OPT("me-steps")) {
|
|
char * tailptr = NULL;
|
|
long steps = strtol(value, &tailptr, 0);
|
|
|
|
if (*tailptr != '\0') {
|
|
fprintf(stderr, "Invalid me-steps value: \"%s\"", value);
|
|
return 0;
|
|
}
|
|
if (steps < -1 || steps > UINT32_MAX) {
|
|
fprintf(stderr, "me-steps value is out of bounds: \"%s\"", value);
|
|
return 0;
|
|
}
|
|
|
|
cfg->me_max_steps = (uint32_t)steps;
|
|
}
|
|
else if (OPT("fast-residual-cost"))
|
|
cfg->fast_residual_cost_limit = atoi(value);
|
|
else if (OPT("vaq")) {
|
|
cfg->vaq = (int)atoi(value);
|
|
}
|
|
else if (OPT("max-merge")) {
|
|
int max_merge = atoi(value);
|
|
if (max_merge < 1 || max_merge > 6) {
|
|
fprintf(stderr, "max-merge needs to be between 1 and 6\n");
|
|
return 0;
|
|
}
|
|
cfg->max_merge = (uint8_t)max_merge;
|
|
}
|
|
else if OPT("early-skip") {
|
|
cfg->early_skip = (bool)atobool(value);
|
|
}
|
|
else if OPT("ml-pu-depth-intra") {
|
|
cfg->ml_pu_depth_intra = (bool)atobool(value);
|
|
}
|
|
else if OPT("partial-coding") {
|
|
uint32_t firstCTU_x;
|
|
uint32_t firstCTU_y;
|
|
uint32_t fullWidth;
|
|
uint32_t fullHeight;
|
|
if (4 != sscanf(value, "%u!%u!%u!%u", &firstCTU_x,
|
|
&firstCTU_y, &fullWidth, &fullHeight)) {
|
|
fprintf(stderr, "invalid partial-coding options. Expected \"%%u!%%u!%%u!%%u\", but got \"%s\"\n", value);
|
|
return 0;
|
|
}
|
|
cfg->partial_coding.startCTU_x = firstCTU_x;
|
|
cfg->partial_coding.startCTU_y = firstCTU_y;
|
|
cfg->partial_coding.fullWidth = fullWidth;
|
|
cfg->partial_coding.fullHeight = fullHeight;
|
|
}
|
|
else if OPT("zero-coeff-rdo") {
|
|
cfg->zero_coeff_rdo = (bool)atobool(value);
|
|
}
|
|
else if OPT("rc-algorithm") {
|
|
int8_t rc_algorithm = 0;
|
|
if (!parse_enum(value, rc_algorithm_names, &rc_algorithm)) {
|
|
fprintf(stderr, "Invalid rate control algorithm %s. Valid values include %s, %s, and %s\n", value,
|
|
rc_algorithm_names[0],
|
|
rc_algorithm_names[1],
|
|
rc_algorithm_names[2]);
|
|
return 0;
|
|
}
|
|
cfg->rc_algorithm = rc_algorithm;
|
|
}
|
|
else if OPT("intra-bits") {
|
|
cfg->intra_bit_allocation = atobool(value);
|
|
}
|
|
else if OPT("clip-neighbour") {
|
|
cfg->clip_neighbour = atobool(value);
|
|
}
|
|
else if OPT("input-file-format") {
|
|
int8_t file_format = 0;
|
|
if (!parse_enum(value, file_format_names, &file_format)) {
|
|
fprintf(stderr, "Invalid input file format %s. Valid values include %s, %s, and %s\n", value,
|
|
file_format_names[0],
|
|
file_format_names[1],
|
|
file_format_names[2]);
|
|
return 0;
|
|
}
|
|
cfg->file_format = file_format;
|
|
}
|
|
else if OPT("stats-file-prefix") {
|
|
cfg->stats_file_prefix = strdup(value);
|
|
}
|
|
else if OPT("chroma-qp-in") {
|
|
memset(cfg->chroma_scale_in[0], 0, 17);
|
|
const bool success = parse_qp_scale_array(value, cfg->chroma_scale_in[0]);
|
|
parse_qp_map(cfg, 0);
|
|
return success;
|
|
}
|
|
else if OPT("chroma-qp-out") {
|
|
memset(cfg->chroma_scale_out[0], 0, 17);
|
|
const bool success = parse_qp_scale_array(value, cfg->chroma_scale_out[0]);
|
|
parse_qp_map(cfg, 0);
|
|
return success;
|
|
}
|
|
else if OPT("mrl") {
|
|
cfg->mrl = atobool(value);
|
|
}
|
|
else if OPT("mip") {
|
|
cfg->mip = atobool(value);
|
|
}
|
|
else if OPT("lfnst") {
|
|
cfg->lfnst = atobool(value);
|
|
}
|
|
else if OPT("isp") {
|
|
cfg->isp = atobool(value);
|
|
}
|
|
else if OPT("jccr") {
|
|
cfg->jccr = (bool)atobool(value);
|
|
}
|
|
else if OPT("amvr") {
|
|
cfg->amvr = (bool)atobool(value);
|
|
}
|
|
else if OPT("cclm") {
|
|
cfg->cclm = (bool)atobool(value);
|
|
}
|
|
else if OPT("combine-intra-cus") {
|
|
cfg->combine_intra_cus = atobool(value);
|
|
}
|
|
else if OPT("force-inter") {
|
|
cfg->force_inter = atobool(value);
|
|
}
|
|
else if OPT("cabac-debug-file") {
|
|
cfg->cabac_debug_file_name = strdup(value);
|
|
if(cfg->cabac_debug_file_name == NULL) {
|
|
fprintf(stderr, "Failed to allocate memory for cabac debug file name.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("dual-tree") {
|
|
cfg->dual_tree = atobool(value);
|
|
}
|
|
else if OPT("mtt-depth-intra") {
|
|
cfg->max_btt_depth[0] = atoi(value);
|
|
}
|
|
else if OPT("mtt-depth-intra-chroma") {
|
|
cfg->max_btt_depth[2] = atoi(value);
|
|
}
|
|
else if OPT("mtt-depth-inter") {
|
|
cfg->max_btt_depth[1] = atoi(value);
|
|
}
|
|
else if OPT("max-bt-size") {
|
|
uint8_t sizes[3];
|
|
const int got = parse_array(value, sizes, 3, 0, 128);
|
|
if (got == 1) {
|
|
cfg->max_bt_size[0] = sizes[0];
|
|
cfg->max_bt_size[1] = sizes[0];
|
|
cfg->max_bt_size[2] = sizes[0];
|
|
}
|
|
else if (got == 3) {
|
|
cfg->max_bt_size[0] = sizes[0];
|
|
cfg->max_bt_size[1] = sizes[1];
|
|
cfg->max_bt_size[2] = sizes[2];
|
|
} else {
|
|
fprintf(stderr, "Incorrect amount of values provided for max-bt-size\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("max-tt-size") {
|
|
uint8_t sizes[3];
|
|
const int got = parse_array(value, sizes, 3, 0, 128);
|
|
if (got == 1) {
|
|
cfg->max_tt_size[0] = sizes[0];
|
|
cfg->max_tt_size[1] = sizes[0];
|
|
cfg->max_tt_size[2] = sizes[0];
|
|
}
|
|
else if (got == 3) {
|
|
cfg->max_tt_size[0] = sizes[0];
|
|
cfg->max_tt_size[1] = sizes[1];
|
|
cfg->max_tt_size[2] = sizes[2];
|
|
} else {
|
|
fprintf(stderr, "Incorrect amount of values provided for max-tt-size\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if OPT("intra-rough-granularity") {
|
|
cfg->intra_rough_search_levels = atoi(value);
|
|
}
|
|
else if OPT ("ibc") {
|
|
int ibc_value = atoi(value);
|
|
if (ibc_value < 0 || ibc_value > 2) {
|
|
fprintf(stderr, "ibc supports only range from 0 to 2\n");
|
|
return 0;
|
|
}
|
|
cfg->ibc = (uint8_t)ibc_value;
|
|
}
|
|
else if OPT("dep-quant") {
|
|
cfg->dep_quant = (bool)atobool(value);
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
#undef OPT
|
|
|
|
return 1;
|
|
}
|
|
|
|
void uvg_config_process_lp_gop(uvg_config *cfg)
|
|
{
|
|
struct {
|
|
unsigned g;
|
|
unsigned d;
|
|
unsigned t;
|
|
} gop;
|
|
|
|
gop.g = cfg->gop_len;
|
|
gop.d = cfg->gop_lp_definition.d;
|
|
gop.t = cfg->gop_lp_definition.t;
|
|
|
|
// Initialize modulos for testing depth.
|
|
// The picture belong to the lowest depth in which (poc % modulo) == 0.
|
|
unsigned depth_modulos[8] = { 0 };
|
|
for (uint32_t d = 0; d < gop.d; ++d) {
|
|
depth_modulos[gop.d - 1 - d] = 1 << d;
|
|
}
|
|
depth_modulos[0] = gop.g;
|
|
|
|
cfg->gop_lowdelay = 1;
|
|
cfg->gop_len = gop.g;
|
|
for (uint32_t g = 1; g <= gop.g; ++g) {
|
|
uvg_gop_config *gop_pic = &cfg->gop[g - 1];
|
|
|
|
// Find gop depth for picture.
|
|
uint32_t gop_layer = 1;
|
|
while (gop_layer < gop.d && (g % depth_modulos[gop_layer - 1])) {
|
|
++gop_layer;
|
|
}
|
|
|
|
gop_pic->poc_offset = g;
|
|
gop_pic->layer = gop_layer;
|
|
gop_pic->qp_offset = gop_layer;
|
|
gop_pic->ref_pos_count = 0;
|
|
gop_pic->ref_neg_count = cfg->ref_frames;
|
|
gop_pic->is_ref = 0;
|
|
|
|
// Set first ref to point to previous frame, and the rest to previous
|
|
// key-frames.
|
|
// If gop.t > 1, have (poc % gop.t) == 0 point gop.t frames away,
|
|
// instead of the previous frame. Set the frames in between to
|
|
// point to the nearest frame with a lower gop-depth.
|
|
if (gop.t > 1) {
|
|
if (gop_pic->poc_offset % gop.t == 0) {
|
|
gop_pic->ref_neg[0] = gop.t;
|
|
} else {
|
|
int r = gop_pic->poc_offset - 1;
|
|
while (r > 0) {
|
|
if (cfg->gop[r].layer < gop_pic->layer) break;
|
|
--r;
|
|
}
|
|
// Var r is now 0 or index of the pic with layer < depth.
|
|
if (cfg->gop[r].layer < gop_pic->layer) {
|
|
gop_pic->ref_neg[0] = gop_pic->poc_offset - cfg->gop[r].poc_offset;
|
|
cfg->gop[r].is_ref = 1;
|
|
} else {
|
|
// No ref was found, just refer to the previous key-frame.
|
|
gop_pic->ref_neg[0] = gop_pic->poc_offset % gop.g;
|
|
}
|
|
}
|
|
} else {
|
|
gop_pic->ref_neg[0] = 1;
|
|
if (gop_pic->poc_offset >= 2) {
|
|
cfg->gop[gop_pic->poc_offset - 2].is_ref = 1;
|
|
}
|
|
}
|
|
|
|
int keyframe = gop_pic->poc_offset;
|
|
for (int i = 1; i < gop_pic->ref_neg_count; ++i) {
|
|
while (keyframe == gop_pic->ref_neg[i - 1]) {
|
|
keyframe += gop.g;
|
|
}
|
|
gop_pic->ref_neg[i] = keyframe;
|
|
}
|
|
|
|
gop_pic->qp_factor = 0.4624; // from HM
|
|
}
|
|
|
|
for (uint32_t g = 0; g < gop.g; ++g) {
|
|
uvg_gop_config *gop_pic = &cfg->gop[g];
|
|
if (!gop_pic->is_ref) {
|
|
gop_pic->qp_factor = 0.68 * 1.31; // derived from HM
|
|
}
|
|
}
|
|
|
|
// Key-frame is always a reference.
|
|
cfg->gop[gop.g - 1].is_ref = 1;
|
|
cfg->gop[gop.g - 1].qp_factor = 0.578; // from HM
|
|
}
|
|
|
|
// forward declaration
|
|
static int validate_hevc_level(uvg_config *const cfg);
|
|
|
|
/**
|
|
* \brief Check that configuration is sensible.
|
|
*
|
|
* \param cfg config to check
|
|
* \return 1 if the config is ok, otherwise 1
|
|
*/
|
|
int uvg_config_validate(const uvg_config *const cfg)
|
|
{
|
|
int error = 0;
|
|
|
|
if (cfg->vaq < 0) {
|
|
fprintf(stderr, "vaq strength must be positive\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->width <= 0) {
|
|
fprintf(stderr, "Input error: width must be positive\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->height <= 0) {
|
|
fprintf(stderr, "Input error: height must be positive\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->width % 2 != 0) {
|
|
fprintf(stderr, "Input error: width must be a multiple of two\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->height % 2 != 0) {
|
|
fprintf(stderr, "Input error: height must be a multiple of two\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->width > 0 && cfg->height > 0) {
|
|
// We must be able to store the total number of luma and chroma pixels
|
|
// in an int32_t. For 4:4:4 chroma mode, the number of pixels is
|
|
// 3 * width * height. Width and height are rounded up to a multiple of
|
|
// LCU size.
|
|
const uint32_t max_lcus = INT_MAX / (3 * LCU_WIDTH * LCU_WIDTH);
|
|
const uint64_t num_lcus = CEILDIV((uint64_t)cfg->width, LCU_WIDTH) *
|
|
CEILDIV((uint64_t)cfg->height, LCU_WIDTH);
|
|
if (num_lcus > max_lcus) {
|
|
fprintf(stderr, "Input error: resolution %dx%d too large (max %u CTUs)\n",
|
|
cfg->width, cfg->height, max_lcus);
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (cfg->framerate_num < 0) {
|
|
fprintf(stderr, "Input error: --input-fps must >=0\n");
|
|
error = 1;
|
|
}
|
|
if (cfg->framerate_denom <= 0) {
|
|
fprintf(stderr, "Input error: --input-fps denominator must be >0\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->gop_len &&
|
|
cfg->intra_period > 1 &&
|
|
!cfg->gop_lowdelay &&
|
|
cfg->intra_period % cfg->gop_len != 0)
|
|
{
|
|
fprintf(stderr,
|
|
"Input error: intra period (%d) not a multiple of B-gop length (%d)\n",
|
|
cfg->intra_period,
|
|
cfg->gop_len);
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->ref_frames < 1 || cfg->ref_frames >= MAX_REF_PIC_COUNT) {
|
|
fprintf(stderr, "Input error: --ref out of range [1..%d]\n", MAX_REF_PIC_COUNT - 1);
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->deblock_beta < -6 || cfg->deblock_beta > 6) {
|
|
fprintf(stderr, "Input error: deblock beta parameter out of range [-6..6]\n");
|
|
error = 1;
|
|
}
|
|
if (cfg->deblock_tc < -6 || cfg->deblock_tc > 6) {
|
|
fprintf(stderr, "Input error: deblock tc parameter out of range [-6..6]\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->rdo < 0 || cfg->rdo > 3) {
|
|
fprintf(stderr, "Input error: --rd parameter out of range [0..3]\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->fme_level != 0 && cfg->fme_level > 4) {
|
|
fprintf(stderr, "Input error: invalid --subme parameter (must be in range 0-4)\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->vui.chroma_loc < 0 || cfg->vui.chroma_loc > 5) {
|
|
fprintf(stderr, "Input error: --chromaloc parameter out of range [0..5]\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->owf < -1) {
|
|
fprintf(stderr, "Input error: --owf must be nonnegative or -1\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->qp != CLIP_TO_QP(cfg->qp)) {
|
|
fprintf(stderr, "Input error: --qp parameter out of range [0..51]\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (abs(cfg->intra_qp_offset) > 51) {
|
|
fprintf(stderr, "Input error: --intra-qp-offset out of range [-51..51]\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (cfg->target_bitrate < 0) {
|
|
fprintf(stderr, "Input error: --bitrate must be nonnegative\n");
|
|
error = 1;
|
|
}
|
|
|
|
for( size_t i = 0; i < UVG_MAX_GOP_LAYERS; i++ )
|
|
{
|
|
if( cfg->pu_depth_inter.min[i] < 0 || cfg->pu_depth_inter.max[i] < 0 ) continue;
|
|
|
|
if( !WITHIN( cfg->pu_depth_inter.min[i], PU_DEPTH_INTER_MIN, PU_DEPTH_INTER_MAX ) ||
|
|
!WITHIN( cfg->pu_depth_inter.max[i], PU_DEPTH_INTER_MIN, PU_DEPTH_INTER_MAX ) )
|
|
{
|
|
fprintf( stderr, "Input error: illegal value for --pu-depth-inter (%d-%d)\n",
|
|
cfg->pu_depth_inter.min[i], cfg->pu_depth_inter.max[i] );
|
|
error = 1;
|
|
}
|
|
else if( cfg->pu_depth_inter.min[i] > cfg->pu_depth_inter.max[i] )
|
|
{
|
|
fprintf( stderr, "Input error: Inter PU depth min (%d) > max (%d)\n",
|
|
cfg->pu_depth_inter.min[i], cfg->pu_depth_inter.max[i] );
|
|
error = 1;
|
|
}
|
|
|
|
if( cfg->pu_depth_intra.min[i] < 0 || cfg->pu_depth_intra.max[i] < 0 ) continue;
|
|
|
|
if( !WITHIN( cfg->pu_depth_intra.min[i], PU_DEPTH_INTRA_MIN, PU_DEPTH_INTRA_MAX ) ||
|
|
!WITHIN( cfg->pu_depth_intra.max[i], PU_DEPTH_INTRA_MIN, PU_DEPTH_INTRA_MAX ) )
|
|
{
|
|
fprintf( stderr, "Input error: illegal value for --pu-depth-intra (%d-%d)\n",
|
|
cfg->pu_depth_intra.min[i], cfg->pu_depth_intra.max[i] );
|
|
error = 1;
|
|
}
|
|
else if( cfg->pu_depth_intra.min[i] > cfg->pu_depth_intra.max[i] )
|
|
{
|
|
fprintf( stderr, "Input error: Intra PU depth min (%d) > max (%d)\n",
|
|
cfg->pu_depth_intra.min[i], cfg->pu_depth_intra.max[i] );
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
// 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 - 1; ++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);
|
|
error = 1;
|
|
break;
|
|
}
|
|
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);
|
|
error = 1;
|
|
break;
|
|
}
|
|
prev_tile_split = cfg->tiles_width_split[i];
|
|
}
|
|
if (cfg->tiles_width_split[cfg->tiles_width_count - 2] >= 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 - 2], cfg->width);
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (cfg->tiles_height_split) {
|
|
int i;
|
|
int32_t prev_tile_split = 0;
|
|
for (i=0; i < cfg->tiles_height_count - 1; ++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);
|
|
error = 1;
|
|
break;
|
|
}
|
|
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);
|
|
error = 1;
|
|
break;
|
|
}
|
|
prev_tile_split = cfg->tiles_height_split[i];
|
|
}
|
|
|
|
if (cfg->tiles_height_split[cfg->tiles_height_count - 2] >= 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 - 2], cfg->height);
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (cfg->implicit_rdpcm && !cfg->lossless) {
|
|
fprintf(stderr, "Input error: --implicit-rdpcm is not suppoted without --lossless\n");
|
|
error = 1;
|
|
}
|
|
|
|
if ((cfg->slices & UVG_SLICES_WPP) && !cfg->wpp) {
|
|
fprintf(stderr, "Input error: --slices=wpp does not work without --wpp.\n");
|
|
error = 1;
|
|
}
|
|
|
|
if ((cfg->scaling_list == UVG_SCALING_LIST_CUSTOM) && !cfg->cqmfile) {
|
|
fprintf(stderr, "Input error: --scaling-list=custom does not work without --cqmfile=<FILE>.\n");
|
|
error = 1;
|
|
}
|
|
|
|
if (validate_hevc_level((uvg_config *const) cfg)) {
|
|
// a level error found and it's not okay
|
|
error = 1;
|
|
}
|
|
|
|
if(cfg->target_bitrate > 0 && cfg->rc_algorithm == UVG_NO_RC) {
|
|
fprintf(stderr, "Bitrate set but rc-algorithm is turned off.\n");
|
|
error = 1;
|
|
}
|
|
|
|
if(cfg->target_bitrate == 0 && cfg->rc_algorithm != UVG_NO_RC) {
|
|
fprintf(stderr, "Rate control algorithm set but bitrate not set.\n");
|
|
error = 1;
|
|
}
|
|
|
|
for (int index = 0; index < 3; index++) {
|
|
int i = 0;
|
|
if (cfg->chroma_scale_in[index][i] != cfg->chroma_scale_out[index][i]) {
|
|
fprintf(stderr, "The starting points of chroma qp scaling list %d do not match. %d != %d",
|
|
index, cfg->chroma_scale_in[index][i], cfg->chroma_scale_out[index][i]);
|
|
error = 1;
|
|
}
|
|
for (; cfg->chroma_scale_in[index][i] != -1; i++);
|
|
if (cfg->chroma_scale_out[index][i] != -1) {
|
|
fprintf(stderr, "The chroma qp scaling lists of index %d are different lengths.\n", index);
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if(cfg->owf != 0 && cfg->cabac_debug_file_name) {
|
|
fprintf(stderr, "OWF and cabac debugging are not supported at the same time.\n");
|
|
error = 1;
|
|
}
|
|
|
|
if(cfg->chroma_trskip_enable && !cfg->trskip_enable) {
|
|
fprintf(stderr, "Transform skip has to be enabled when chroma transform skip is enabled.\n");
|
|
error = 1;
|
|
}
|
|
|
|
if(cfg->intra_rough_search_levels > 4) {
|
|
fprintf(stderr, "intra-rough-granularity must be between [0..4].\n");
|
|
error = 1;
|
|
}
|
|
|
|
return !error;
|
|
}
|
|
|
|
static int validate_hevc_level(uvg_config *const cfg) {
|
|
static const struct { uint32_t lsr; uint32_t lps; uint32_t main_bitrate; } LEVEL_CONSTRAINTS[13] = {
|
|
{ 552960, 36864, 128 }, // 1
|
|
|
|
{ 3686400, 122880, 1500 }, // 2
|
|
{ 7372800, 245760, 3000 }, // 2.1
|
|
|
|
{ 16588800, 552960, 6000 }, // 3
|
|
{ 33177600, 983040, 10000 }, // 3.1
|
|
|
|
{ 66846720, 2228224, 12000 }, // 4
|
|
{ 133693440, 2228224, 20000 }, // 4.1
|
|
|
|
{ 267386880, 8912896, 25000 }, // 5
|
|
{ 534773760, 8912896, 40000 }, // 5.1
|
|
{ 1069547520, 8912896, 60000 }, // 5.2
|
|
|
|
{ 1069547520, 35651584, 60000 }, // 6
|
|
{ 2139095040, 35651584, 120000 }, // 6.1
|
|
{ 4278190080, 35651584, 240000 }, // 6.2
|
|
};
|
|
|
|
// bit rates for the high-tiers of the levels from 4 to 6.2
|
|
static const uint32_t HIGH_TIER_BITRATES[8] = {
|
|
30000, 50000, 100000, 160000, 240000, 240000, 480000, 800000
|
|
};
|
|
|
|
int level_error = 0;
|
|
|
|
const char* level_err_prefix;
|
|
if (cfg->force_level) {
|
|
level_err_prefix = "Level warning";
|
|
} else {
|
|
level_err_prefix = "Level error";
|
|
}
|
|
|
|
uint8_t lvl_idx;
|
|
|
|
// for nicer error print
|
|
float lvl = ((float)cfg->level) / 10.0f;
|
|
|
|
// check if the level is valid and get it's lsr and lps values
|
|
switch (cfg->level) {
|
|
case 10:
|
|
lvl_idx = 0;
|
|
break;
|
|
case 20:
|
|
lvl_idx = 1;
|
|
break;
|
|
case 21:
|
|
lvl_idx = 2;
|
|
break;
|
|
case 30:
|
|
lvl_idx = 3;
|
|
break;
|
|
case 31:
|
|
lvl_idx = 4;
|
|
break;
|
|
case 40:
|
|
lvl_idx = 5;
|
|
break;
|
|
case 41:
|
|
lvl_idx = 6;
|
|
break;
|
|
case 50:
|
|
lvl_idx = 7;
|
|
break;
|
|
case 51:
|
|
lvl_idx = 8;
|
|
break;
|
|
case 52:
|
|
lvl_idx = 9;
|
|
break;
|
|
case 60:
|
|
lvl_idx = 10;
|
|
break;
|
|
case 61:
|
|
lvl_idx = 11;
|
|
break;
|
|
case 62:
|
|
lvl_idx = 12;
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Input error: %g is an invalid level value\n", lvl);
|
|
return 1;
|
|
}
|
|
|
|
if (cfg->high_tier && cfg->level < 40) {
|
|
fprintf(stderr, "Input error: high tier requires at least level 4\n");
|
|
return 1;
|
|
}
|
|
|
|
// max luma sample rate
|
|
uint32_t max_lsr = LEVEL_CONSTRAINTS[lvl_idx].lsr;
|
|
|
|
// max luma picture size
|
|
uint32_t max_lps = LEVEL_CONSTRAINTS[lvl_idx].lps;
|
|
|
|
if (cfg->high_tier) {
|
|
cfg->max_bitrate = HIGH_TIER_BITRATES[lvl_idx - 5] * 1000;
|
|
} else {
|
|
cfg->max_bitrate = LEVEL_CONSTRAINTS[lvl_idx].main_bitrate * 1000;
|
|
}
|
|
|
|
if (cfg->target_bitrate > (int32_t)cfg->max_bitrate) {
|
|
fprintf(stderr, "%s: target bitrate exceeds %i, which is the maximum %s tier level %g bitrate\n",
|
|
level_err_prefix, cfg->max_bitrate, cfg->high_tier?"high":"main", lvl);
|
|
level_error = 1;
|
|
}
|
|
|
|
// check the conformance to the level limits
|
|
|
|
// luma samples
|
|
uint64_t cfg_samples = cfg->width * cfg->height;
|
|
|
|
// luma sample rate
|
|
double framerate = ((double)cfg->framerate_num) / ((double)cfg->framerate_denom);
|
|
uint64_t cfg_sample_rate = cfg_samples * (uint64_t) framerate;
|
|
|
|
// square of the maximum allowed dimension
|
|
uint32_t max_dimension_squared = 8 * max_lps;
|
|
|
|
// check maximum dimensions
|
|
if (cfg->width * cfg->width > (int32_t)max_dimension_squared) {
|
|
uint32_t max_dim = (uint32_t)sqrtf((float)max_dimension_squared);
|
|
fprintf(stderr, "%s: picture width of %i is too large for this level (%g), maximum dimension is %i\n",
|
|
level_err_prefix, cfg->width, lvl, max_dim);
|
|
level_error = 1;
|
|
}
|
|
if (cfg->height * cfg->height > (int32_t)max_dimension_squared) {
|
|
uint32_t max_dim = (uint32_t)sqrtf((float)max_dimension_squared);
|
|
fprintf(stderr, "%s: picture height of %i is too large for this level (%g), maximum dimension is %i\n",
|
|
level_err_prefix, cfg->height, lvl, max_dim);
|
|
level_error = 1;
|
|
}
|
|
|
|
// check luma picture size
|
|
if (cfg_samples > max_lps) {
|
|
fprintf(stderr, "%s: picture resolution of %ix%i is too large for this level (%g) (it has %llu samples, maximum is %u samples)\n",
|
|
level_err_prefix, cfg->width, cfg->height, lvl, (unsigned long long) cfg_samples, max_lps);
|
|
level_error = 1;
|
|
}
|
|
|
|
// check luma sample rate
|
|
if (cfg_sample_rate > max_lsr) {
|
|
fprintf(stderr, "%s: framerate of %g is too big for this level (%g) and picture resolution (it has the sample rate of %llu, maximum is %u\n",
|
|
level_err_prefix, framerate, lvl, (unsigned long long) cfg_sample_rate, max_lsr);
|
|
level_error = 1;
|
|
}
|
|
|
|
if (cfg->force_level) {
|
|
// we wanted to print warnings, not get errors
|
|
return 0;
|
|
} else {
|
|
return level_error;
|
|
}
|
|
}
|