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;