Command-line option for automatic fixed QP offsets The new command-line option is: --use-fixed-qp-offsets=1, which will trigger the use of fixed QP offsets. Offsets are either: - User specified when --fixed-qp-offsets=a,b,c,d,e is also given, OR - Automatic when --fixed-qp-offsets are NOT given. Implementation mimics the QP offsets used by default 1-pass encode for Alt-ref and internal ARFs. And the QP offset for keyframes is chosen empirically. Change-Id: I5430baa63607ff0ca34d54765aec4f8ced5089a5 (cherry picked from commit 64e34af53943d138a35a15bfca8eec13390d3bf5)
diff --git a/aom/aom_encoder.h b/aom/aom_encoder.h index f36f066..23019ba 100644 --- a/aom/aom_encoder.h +++ b/aom/aom_encoder.h
@@ -865,6 +865,20 @@ */ int tile_heights[MAX_TILE_HEIGHTS]; + /*!\brief Whether encoder should use fixed QP offsets. + * + * If a value of 1 is provided, encoder will use fixed QP offsets for frames + * at different levels of the pyramid. + * - If 'fixed_qp_offsets' is also provided, encoder will use the given + * offsets + * - If not, encoder will select the fixed offsets based on the cq-level + * provided. + * If a value of 0 is provided and fixed_qp_offset are not provided, encoder + * will NOT use fixed QP offsets. + * Note: This option is only relevant for --end-usage=q. + */ + unsigned int use_fixed_qp_offsets; + /*!\brief Number of fixed QP offsets * * This defines the number of elements in the fixed_qp_offsets array.
diff --git a/apps/aomenc.c b/apps/aomenc.c index c7f9184..663c790 100644 --- a/apps/aomenc.c +++ b/apps/aomenc.c
@@ -799,6 +799,14 @@ "operating points conforms to. " "Bit value 0(defualt): Main Tier; 1: High Tier."); +static const arg_def_t use_fixed_qp_offsets = + ARG_DEF(NULL, "use-fixed-qp-offsets", 1, + "Enable fixed QP offsets for frames at different levels of the " + "pyramid. Selected automatically from --cq-level if " + "--fixed-qp-offsets is not provided. If this option is not " + "specified (default), offsets are adaptively chosen by the " + "encoder."); + static const arg_def_t fixed_qp_offsets = ARG_DEF(NULL, "fixed-qp-offsets", 1, "Set fixed QP offsets for frames at different levels of the " @@ -1582,6 +1590,8 @@ } else if (arg_match(&arg, &vmaf_model_path, argi)) { config->vmaf_model_path = arg.val; #endif + } else if (arg_match(&arg, &use_fixed_qp_offsets, argi)) { + config->cfg.use_fixed_qp_offsets = arg_parse_uint(&arg); } else if (arg_match(&arg, &fixed_qp_offsets, argi)) { const int fixed_qp_offset_count = arg_parse_list( &arg, config->cfg.fixed_qp_offsets, FIXED_QP_OFFSET_COUNT); @@ -1590,6 +1600,7 @@ "only %d values were provided.\n", FIXED_QP_OFFSET_COUNT, fixed_qp_offset_count); } + config->cfg.use_fixed_qp_offsets = 1; } else if (global->usage == AOM_USAGE_REALTIME && arg_match(&arg, &enable_restoration, argi)) { if (arg_parse_uint(&arg) == 1) {
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c index 8480776..740807c 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -480,10 +480,14 @@ } if (cfg->rc_end_usage == AOM_Q) { + RANGE_CHECK_HI(cfg, use_fixed_qp_offsets, 1); for (int i = 0; i < FIXED_QP_OFFSET_COUNT; ++i) { RANGE_CHECK_HI(cfg, fixed_qp_offsets[i], 63); } } else { + if (cfg->use_fixed_qp_offsets > 0) { + ERROR("--use_fixed_qp_offsets can only be used with --end-usage=q"); + } for (int i = 0; i < FIXED_QP_OFFSET_COUNT; ++i) { if (cfg->fixed_qp_offsets[i] >= 0) { ERROR("--fixed_qp_offsets can only be used with --end-usage=q"); @@ -661,6 +665,23 @@ extra_cfg->reduced_tx_type_set = cfg->reduced_tx_type_set; } +static double convert_qp_offset(int cq_level, int q_offset, int bit_depth) { + const double base_q_val = av1_convert_qindex_to_q(cq_level, bit_depth); + const int new_q_index_offset = av1_quantizer_to_qindex(q_offset); + const int new_q_index = AOMMAX(cq_level - new_q_index_offset, 0); + const double new_q_val = av1_convert_qindex_to_q(new_q_index, bit_depth); + return (base_q_val - new_q_val); +} + +static double get_modeled_qp_offset(int cq_level, int level, int bit_depth) { + // 80% for keyframe was derived empirically. + // 40% similar to rc_pick_q_and_bounds_one_pass_vbr() for Q mode ARF. + // Rest derived similar to rc_pick_q_and_bounds_two_pass() + static const int percents[FIXED_QP_OFFSET_COUNT] = { 80, 60, 30, 15, 8 }; + const double q_val = av1_convert_qindex_to_q(cq_level, bit_depth); + return q_val * percents[level] / 100; +} + static aom_codec_err_t set_encoder_config(AV1EncoderConfig *oxcf, const aom_codec_enc_cfg_t *cfg, struct av1_extracfg *extra_cfg) { @@ -982,14 +1003,19 @@ sizeof(oxcf->target_seq_level_idx)); oxcf->tier_mask = extra_cfg->tier_mask; - oxcf->use_fixed_qp_offsets = (oxcf->rc_mode == AOM_Q); + oxcf->use_fixed_qp_offsets = + cfg->use_fixed_qp_offsets && (oxcf->rc_mode == AOM_Q); for (int i = 0; i < FIXED_QP_OFFSET_COUNT; ++i) { - if (cfg->fixed_qp_offsets[i] >= 0) { - oxcf->fixed_qp_offsets[i] = - av1_quantizer_to_qindex(cfg->fixed_qp_offsets[i]); + if (oxcf->use_fixed_qp_offsets) { + if (cfg->fixed_qp_offsets[i] >= 0) { // user-provided qp offset + oxcf->fixed_qp_offsets[i] = convert_qp_offset( + oxcf->cq_level, cfg->fixed_qp_offsets[i], oxcf->bit_depth); + } else { // auto-selected qp offset + oxcf->fixed_qp_offsets[i] = + get_modeled_qp_offset(oxcf->cq_level, i, oxcf->bit_depth); + } } else { - oxcf->fixed_qp_offsets[i] = -1; - oxcf->use_fixed_qp_offsets = 0; + oxcf->fixed_qp_offsets[i] = -1.0; } } @@ -2780,6 +2806,7 @@ 0, // tile_height_count { 0 }, // tile_widths { 0 }, // tile_heights + 0, // use_fixed_qp_offsets { -1, -1, -1, -1, -1 }, // fixed_qp_offsets { 0, 128, 128, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // cfg @@ -2850,6 +2877,7 @@ 0, // tile_height_count { 0 }, // tile_widths { 0 }, // tile_heights + 0, // use_fixed_qp_offsets { -1, -1, -1, -1, -1 }, // fixed_qp_offsets { 0, 128, 128, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // cfg
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h index e28be4a..84a85e5 100644 --- a/av1/encoder/encoder.h +++ b/av1/encoder/encoder.h
@@ -428,13 +428,14 @@ // Bit mask to specify which tier each of the 32 possible operating points // conforms to. unsigned int tier_mask; - // Derived from 'fixed_qp_offsets' by setting it to true iff all values in - // that array are valid (>=0). + // If true, encoder will use fixed QP offsets, that are either: + // - Given by the user, and stored in 'fixed_qp_offsets' array, OR + // - Picked automatically from cq_level. int use_fixed_qp_offsets; // List of QP offsets for: keyframe, ALTREF, and 3 levels of internal ARFs. // If any of these values are negative, fixed offsets are disabled. - // Uses internal qindex range: 0 to 255. - int fixed_qp_offsets[FIXED_QP_OFFSET_COUNT]; + // Uses internal q range. + double fixed_qp_offsets[FIXED_QP_OFFSET_COUNT]; // min_cr / 100 is the target minimum compression ratio for each frame. unsigned int min_cr; const cfg_options_t *encoder_cfg;
diff --git a/av1/encoder/ratectrl.c b/av1/encoder/ratectrl.c index 20b726c..f0a107d 100644 --- a/av1/encoder/ratectrl.c +++ b/av1/encoder/ratectrl.c
@@ -893,16 +893,17 @@ } static int get_q_using_fixed_offsets(const AV1EncoderConfig *const oxcf, + const RATE_CONTROL *const rc, const GF_GROUP *const gf_group, int gf_index, int cq_level, - int frames_to_key) { + int bit_depth) { assert(oxcf->use_fixed_qp_offsets); assert(oxcf->rc_mode == AOM_Q); const FRAME_UPDATE_TYPE update_type = gf_group->update_type[gf_index]; int offset_idx = -1; if (update_type == KF_UPDATE) { - if (frames_to_key == 1) { + if (rc->frames_to_key == 1) { // Image / intra-only coding: ignore offsets. return cq_level; } @@ -920,7 +921,13 @@ assert(offset_idx >= 0 && offset_idx < FIXED_QP_OFFSET_COUNT); assert(oxcf->fixed_qp_offsets[offset_idx] >= 0); - return AOMMAX(cq_level - oxcf->fixed_qp_offsets[offset_idx], 0); + // Get qindex offset, by first converting to 'q' and then back. + const double q_val_orig = av1_convert_qindex_to_q(cq_level, bit_depth); + const double q_val_target = + AOMMAX(q_val_orig - oxcf->fixed_qp_offsets[offset_idx], 0.0); + const int delta_qindex = + av1_compute_qdelta(rc, q_val_orig, q_val_target, bit_depth); + return AOMMAX(cq_level + delta_qindex, 0); } static int rc_pick_q_and_bounds_one_pass_vbr(const AV1_COMP *cpi, int width, @@ -932,17 +939,17 @@ const AV1EncoderConfig *const oxcf = &cpi->oxcf; const int cq_level = get_active_cq_level(rc, oxcf, frame_is_intra_only(cm), cm->superres_scale_denominator); + const int bit_depth = cm->seq_params.bit_depth; if (oxcf->use_fixed_qp_offsets) { - return get_q_using_fixed_offsets(oxcf, &cpi->gf_group, cpi->gf_group.index, - cq_level, rc->frames_to_key); + return get_q_using_fixed_offsets(oxcf, rc, &cpi->gf_group, + cpi->gf_group.index, cq_level, bit_depth); } int active_best_quality; int active_worst_quality = calc_active_worst_quality_one_pass_vbr(cpi); int q; int *inter_minq; - const int bit_depth = cm->seq_params.bit_depth; ASSIGN_MINQ_TABLE(bit_depth, inter_minq); if (frame_is_intra_only(cm)) { @@ -1383,10 +1390,11 @@ const GF_GROUP *gf_group = &cpi->gf_group; const int cq_level = get_active_cq_level(rc, oxcf, frame_is_intra_only(cm), cm->superres_scale_denominator); + const int bit_depth = cm->seq_params.bit_depth; if (oxcf->use_fixed_qp_offsets) { - return get_q_using_fixed_offsets(oxcf, gf_group, gf_group->index, cq_level, - rc->frames_to_key); + return get_q_using_fixed_offsets(oxcf, rc, gf_group, gf_group->index, + cq_level, bit_depth); } int active_best_quality = 0;