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;