Add command-line option for fixed QP offsets.

Cherry-picked from experimental

Change-Id: I54db1ec34dc6667e6617d79212361032abd97f08
diff --git a/aom/aom_encoder.h b/aom/aom_encoder.h
index a4e5f70..f36f066 100644
--- a/aom/aom_encoder.h
+++ b/aom/aom_encoder.h
@@ -865,6 +865,28 @@
    */
   int tile_heights[MAX_TILE_HEIGHTS];
 
+/*!\brief Number of fixed QP offsets
+ *
+ * This defines the number of elements in the fixed_qp_offsets array.
+ */
+#define FIXED_QP_OFFSET_COUNT 5
+
+  /*!\brief Array of fixed QP offsets
+   *
+   * This array specifies fixed QP offsets (range: 0 to 63) for frames at
+   * different levels of the pyramid. It is a comma-separated list of 5 values:
+   * - QP offset for keyframe
+   * - QP offset for ALTREF frame
+   * - QP offset for 1st level internal ARF
+   * - QP offset for 2nd level internal ARF
+   * - QP offset for 3rd level internal ARF
+   * Notes:
+   * - QP offset for leaf level frames is not explicitly specified. These frames
+   *   use the worst quality allowed (--cq-level).
+   * - This option is only relevant for --end-usage=q.
+   */
+  int fixed_qp_offsets[FIXED_QP_OFFSET_COUNT];
+
   /*!\brief Options defined per config file
    *
    */
diff --git a/apps/aomenc.c b/apps/aomenc.c
index 6b76146..fb2ff6e 100644
--- a/apps/aomenc.c
+++ b/apps/aomenc.c
@@ -802,6 +802,14 @@
             "operating points conforms to. "
             "Bit value 0(defualt): Main Tier; 1: High Tier.");
 
+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 "
+            "pyramid. Comma-separated list of 5 offsets for keyframe, ALTREF, "
+            "and 3 levels of internal alt-refs. If this option is not "
+            "specified (default), offsets are adaptively chosen by the "
+            "encoder.");
+
 static const arg_def_t *av1_args[] = { &cpu_used_av1,
                                        &auto_altref,
                                        &sharpness,
@@ -1583,6 +1591,14 @@
     } else if (arg_match(&arg, &vmaf_model_path, argi)) {
       config->vmaf_model_path = arg.val;
 #endif
+    } 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);
+      if (fixed_qp_offset_count < FIXED_QP_OFFSET_COUNT) {
+        die("Option --fixed_qp_offsets requires %d comma-separated values, but "
+            "only %d values were provided.\n",
+            FIXED_QP_OFFSET_COUNT, fixed_qp_offset_count);
+      }
     } 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 4c6f067..d3cba5e 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -485,6 +485,18 @@
     ERROR("Source bit-depth 12 not supported in profile < 2");
   }
 
+  if (cfg->rc_end_usage == AOM_Q) {
+    for (int i = 0; i < FIXED_QP_OFFSET_COUNT; ++i) {
+      RANGE_CHECK_HI(cfg, fixed_qp_offsets[i], 63);
+    }
+  } else {
+    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");
+      }
+    }
+  }
+
   RANGE_CHECK(extra_cfg, color_primaries, AOM_CICP_CP_BT_709,
               AOM_CICP_CP_EBU_3213);  // Need to check range more precisely to
                                       // check for reserved values?
@@ -987,6 +999,18 @@
   memcpy(oxcf->target_seq_level_idx, extra_cfg->target_seq_level_idx,
          sizeof(oxcf->target_seq_level_idx));
   oxcf->tier_mask = extra_cfg->tier_mask;
+
+  oxcf->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]);
+    } else {
+      oxcf->fixed_qp_offsets[i] = -1;
+      oxcf->use_fixed_qp_offsets = 0;
+    }
+  }
+
   oxcf->min_cr = extra_cfg->min_cr;
   return AOM_CODEC_OK;
 }
@@ -2770,20 +2794,21 @@
         2000,  // rc_two_pass_vbrmax_section
 
         // keyframing settings (kf)
-        0,            // fwd_kf_enabled
-        AOM_KF_AUTO,  // g_kfmode
-        0,            // kf_min_dist
-        9999,         // kf_max_dist
-        0,            // sframe_dist
-        1,            // sframe_mode
-        0,            // large_scale_tile
-        0,            // monochrome
-        0,            // full_still_picture_hdr
-        0,            // save_as_annexb
-        0,            // tile_width_count
-        0,            // tile_height_count
-        { 0 },        // tile_widths
-        { 0 },        // tile_heights
+        0,                       // fwd_kf_enabled
+        AOM_KF_AUTO,             // g_kfmode
+        0,                       // kf_min_dist
+        9999,                    // kf_max_dist
+        0,                       // sframe_dist
+        1,                       // sframe_mode
+        0,                       // large_scale_tile
+        0,                       // monochrome
+        0,                       // full_still_picture_hdr
+        0,                       // save_as_annexb
+        0,                       // tile_width_count
+        0,                       // tile_height_count
+        { 0 },                   // tile_widths
+        { 0 },                   // tile_heights
+        { -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
     } },
@@ -2839,20 +2864,21 @@
         2000,  // rc_two_pass_vbrmax_section
 
         // keyframing settings (kf)
-        0,            // fwd_kf_enabled
-        AOM_KF_AUTO,  // g_kfmode
-        0,            // kf_min_dist
-        9999,         // kf_max_dist
-        0,            // sframe_dist
-        1,            // sframe_mode
-        0,            // large_scale_tile
-        0,            // monochrome
-        0,            // full_still_picture_hdr
-        0,            // save_as_annexb
-        0,            // tile_width_count
-        0,            // tile_height_count
-        { 0 },        // tile_widths
-        { 0 },        // tile_heights
+        0,                       // fwd_kf_enabled
+        AOM_KF_AUTO,             // g_kfmode
+        0,                       // kf_min_dist
+        9999,                    // kf_max_dist
+        0,                       // sframe_dist
+        1,                       // sframe_mode
+        0,                       // large_scale_tile
+        0,                       // monochrome
+        0,                       // full_still_picture_hdr
+        0,                       // save_as_annexb
+        0,                       // tile_width_count
+        0,                       // tile_height_count
+        { 0 },                   // tile_widths
+        { 0 },                   // tile_heights
+        { -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 3513103..274c700 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -431,6 +431,13 @@
   // 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).
+  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];
   // 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 f43279f..7772c68 100644
--- a/av1/encoder/ratectrl.c
+++ b/av1/encoder/ratectrl.c
@@ -887,6 +887,36 @@
   return active_cq_level;
 }
 
+static int get_q_using_fixed_offsets(const AV1EncoderConfig *const oxcf,
+                                     const GF_GROUP *const gf_group,
+                                     int cq_level, int is_keyframe,
+                                     int frames_to_key) {
+  assert(oxcf->use_fixed_qp_offsets);
+  assert(oxcf->rc_mode == AOM_Q);
+  const int frame_index = gf_group->index;
+  const FRAME_UPDATE_TYPE update_type = gf_group->update_type[frame_index];
+
+  int offset_idx = -1;
+  if (is_keyframe) {
+    // Ignore offsets for image coding.
+    if (frames_to_key == 1) return cq_level;
+    offset_idx = 0;
+  } else if (update_type == ARF_UPDATE || update_type == GF_UPDATE) {
+    offset_idx = 1;
+  } else if (update_type == INTNL_ARF_UPDATE) {
+    offset_idx =
+        AOMMIN(gf_group->layer_depth[frame_index], FIXED_QP_OFFSET_COUNT - 1);
+  } else {  // Leaf level / overlay frame.
+    assert(update_type == LF_UPDATE || update_type == OVERLAY_UPDATE ||
+           update_type == INTNL_OVERLAY_UPDATE);
+    return cq_level;  // Directly Return worst quality allowed.
+  }
+  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);
+}
+
 static int rc_pick_q_and_bounds_one_pass_vbr(const AV1_COMP *cpi, int width,
                                              int height, int *bottom_index,
                                              int *top_index) {
@@ -896,6 +926,13 @@
   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);
+
+  if (oxcf->use_fixed_qp_offsets) {
+    return get_q_using_fixed_offsets(oxcf, &cpi->gf_group, cq_level,
+                                     frame_is_intra_only(cm),
+                                     rc->frames_to_key);
+  }
+
   int active_best_quality;
   int active_worst_quality = calc_active_worst_quality_one_pass_vbr(cpi);
   int q;
@@ -1341,6 +1378,12 @@
   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);
+
+  if (oxcf->use_fixed_qp_offsets) {
+    return get_q_using_fixed_offsets(
+        oxcf, gf_group, cq_level, frame_is_intra_only(cm), rc->frames_to_key);
+  }
+
   int active_best_quality = 0;
   int active_worst_quality = rc->active_worst_quality;
   int q;