Add user rating based delta q mode --deltaq-mode=4

The current model is fitted at the near lossless quality.

Change-Id: I0842b34b33f91d3915b3400eb63458c7d704d1e9
diff --git a/aom/aomcx.h b/aom/aomcx.h
index e92c0a7..42b3d66 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1115,6 +1115,7 @@
    * - 1 = use modulation to maximize objective quality (default)
    * - 2 = use modulation for local test
    * - 3 = use modulation for key frame perceptual quality optimization
+   * - 4 = use modulation for user rating based perceptual quality optimization
    */
   AV1E_SET_DELTAQ_MODE = 107,
 
diff --git a/av1/arg_defs.c b/av1/arg_defs.c
index 663c889..f7a36e3 100644
--- a/av1/arg_defs.c
+++ b/av1/arg_defs.c
@@ -517,7 +517,8 @@
   .deltaq_mode =
       ARG_DEF(NULL, "deltaq-mode", 1,
               "Delta qindex mode (0: off, 1: deltaq objective (default), "
-              "2: deltaq placeholder, 3: key frame visual quality). "
+              "2: deltaq placeholder, 3: key frame visual quality, 4: user "
+              "rating based visual quality optimization). "
               "Currently this requires enable-tpl-model as a prerequisite."),
   .deltalf_mode = ARG_DEF(NULL, "delta-lf-mode", 1,
                           "Enable delta-lf-mode (0: off (default), 1: on)"),
diff --git a/av1/encoder/allintra_vis.c b/av1/encoder/allintra_vis.c
index 817c33c..510a28f 100644
--- a/av1/encoder/allintra_vis.c
+++ b/av1/encoder/allintra_vis.c
@@ -13,6 +13,7 @@
 
 #include "av1/encoder/allintra_vis.h"
 #include "av1/encoder/hybrid_fwd_txfm.h"
+#include "av1/encoder/rdopt_utils.h"
 
 // Process the wiener variance in 16x16 block basis.
 static int qsort_comp(const void *elem1, const void *elem2) {
@@ -417,3 +418,84 @@
 
   return qindex;
 }
+
+void av1_init_mb_ur_var_buffer(AV1_COMP *cpi) {
+  AV1_COMMON *cm = &cpi->common;
+
+  if (cpi->mb_variance) return;
+
+  CHECK_MEM_ERROR(cm, cpi->mb_variance,
+                  aom_calloc(cpi->frame_info.mb_rows * cpi->frame_info.mb_cols,
+                             sizeof(*cpi->mb_variance)));
+}
+
+void av1_set_mb_ur_variance(AV1_COMP *cpi) {
+  const CommonModeInfoParams *const mi_params = &cpi->common.mi_params;
+  ThreadData *td = &cpi->td;
+  MACROBLOCK *x = &td->mb;
+  MACROBLOCKD *xd = &x->e_mbd;
+  uint8_t *y_buffer = cpi->source->y_buffer;
+  const int y_stride = cpi->source->y_stride;
+  const int block_size = cpi->common.seq_params->sb_size;
+
+  const int num_mi_w = mi_size_wide[block_size];
+  const int num_mi_h = mi_size_high[block_size];
+  const int num_cols = (mi_params->mi_cols + num_mi_w - 1) / num_mi_w;
+  const int num_rows = (mi_params->mi_rows + num_mi_h - 1) / num_mi_h;
+  const int use_hbd = cpi->source->flags & YV12_FLAG_HIGHBITDEPTH;
+
+  // Loop through each SB block.
+  for (int row = 0; row < num_rows; ++row) {
+    for (int col = 0; col < num_cols; ++col) {
+      double var = 0.0, num_of_var = 0.0;
+      const int index = row * num_cols + col;
+
+      // Loop through each 8x8 block.
+      for (int mi_row = row * num_mi_h;
+           mi_row < mi_params->mi_rows && mi_row < (row + 1) * num_mi_h;
+           mi_row += 2) {
+        for (int mi_col = col * num_mi_w;
+             mi_col < mi_params->mi_cols && mi_col < (col + 1) * num_mi_w;
+             mi_col += 2) {
+          struct buf_2d buf;
+          const int row_offset_y = mi_row << 2;
+          const int col_offset_y = mi_col << 2;
+
+          buf.buf = y_buffer + row_offset_y * y_stride + col_offset_y;
+          buf.stride = y_stride;
+
+          if (use_hbd) {
+            var += av1_high_get_sby_perpixel_variance(cpi, &buf, BLOCK_8X8,
+                                                      xd->bd);
+          } else {
+            var += av1_get_sby_perpixel_variance(cpi, &buf, BLOCK_8X8);
+          }
+
+          num_of_var += 1.0;
+        }
+      }
+      var = var / num_of_var;
+      cpi->mb_variance[index] = var;
+    }
+  }
+}
+
+int av1_get_sbq_user_rating_based(AV1_COMP *const cpi, int mi_row, int mi_col) {
+  const BLOCK_SIZE bsize = cpi->common.seq_params->sb_size;
+  const CommonModeInfoParams *const mi_params = &cpi->common.mi_params;
+  AV1_COMMON *const cm = &cpi->common;
+  const int base_qindex = cm->quant_params.base_qindex;
+  if (base_qindex == 0) return base_qindex;
+  const int num_mi_w = mi_size_wide[bsize];
+  const int num_mi_h = mi_size_high[bsize];
+  const int num_cols = (mi_params->mi_cols + num_mi_w - 1) / num_mi_w;
+  const int index = (mi_row / num_mi_h) * num_cols + (mi_col / num_mi_w);
+  const double var = cpi->mb_variance[index];
+  const double delta_qindex =
+      4.0 * 23.5 * (1.0 - exp(-0.002 * var)) - 2.0 * 23.5;
+  int qindex = (int)((double)base_qindex + delta_qindex + 0.5);
+  qindex = AOMMIN(qindex, MAXQ);
+  qindex = AOMMAX(qindex, MINQ + 1);
+
+  return qindex;
+}
diff --git a/av1/encoder/allintra_vis.h b/av1/encoder/allintra_vis.h
index db6267e..9798707 100644
--- a/av1/encoder/allintra_vis.h
+++ b/av1/encoder/allintra_vis.h
@@ -23,3 +23,10 @@
 
 int av1_get_sbq_perceptual_ai(AV1_COMP *const cpi, BLOCK_SIZE bsize, int mi_row,
                               int mi_col);
+
+// User rating based mode
+void av1_init_mb_ur_var_buffer(AV1_COMP *cpi);
+
+void av1_set_mb_ur_variance(AV1_COMP *cpi);
+
+int av1_get_sbq_user_rating_based(AV1_COMP *const cpi, int mi_row, int mi_col);
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index ed99441..60f8e50 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -239,6 +239,8 @@
         av1_get_q_for_deltaq_objective(cpi, sb_size, mi_row, mi_col);
   } else if (cpi->oxcf.q_cfg.deltaq_mode == DELTA_Q_PERCEPTUAL_AI) {
     current_qindex = av1_get_sbq_perceptual_ai(cpi, sb_size, mi_row, mi_col);
+  } else if (cpi->oxcf.q_cfg.deltaq_mode == DELTA_Q_USER_RATING_BASED) {
+    current_qindex = av1_get_sbq_user_rating_based(cpi, mi_row, mi_col);
   }
 
   MACROBLOCKD *const xd = &x->e_mbd;
@@ -1358,6 +1360,9 @@
       cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
     else if (deltaq_mode == DELTA_Q_PERCEPTUAL_AI)
       cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
+    else if (deltaq_mode == DELTA_Q_USER_RATING_BASED)
+      cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
+
     // Set delta_q_present_flag before it is used for the first time
     cm->delta_q_info.delta_lf_res = DEFAULT_DELTA_LF_RES;
     cm->delta_q_info.delta_q_present_flag = deltaq_mode != NO_DELTA_Q;
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 86ce61d..16d559d 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -1378,6 +1378,7 @@
                              sizeof(*cpi->consec_zero_mv)));
 
   cpi->mb_weber_stats = NULL;
+  cpi->mb_variance = NULL;
 
   {
     const int bsize = BLOCK_16X16;
@@ -3383,6 +3384,11 @@
     av1_set_mb_wiener_variance(cpi);
   }
 
+  if (cpi->oxcf.q_cfg.deltaq_mode == DELTA_Q_USER_RATING_BASED) {
+    av1_init_mb_ur_var_buffer(cpi);
+    av1_set_mb_ur_variance(cpi);
+  }
+
 #if CONFIG_INTERNAL_STATS
   memset(cpi->mode_chosen_counts, 0,
          MAX_MODES * sizeof(*cpi->mode_chosen_counts));
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 5024954..155ee7e 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -154,6 +154,7 @@
   DELTA_Q_OBJECTIVE = 1,      // Modulation to improve objective quality
   DELTA_Q_PERCEPTUAL = 2,     // Modulation to improve video perceptual quality
   DELTA_Q_PERCEPTUAL_AI = 3,  // Perceptual quality opt for all intra mode
+  DELTA_Q_USER_RATING_BASED = 4,  // User rating based delta q mode
   DELTA_Q_MODE_COUNT  // This should always be the last member of the enum
 } UENUM1BYTE(DELTAQ_MODE);
 
@@ -3109,6 +3110,11 @@
   int64_t norm_wiener_variance;
 
   /*!
+   * Buffer to store MB variance.
+   */
+  double *mb_variance;
+
+  /*!
    * Flag to indicate that current frame is dropped.
    */
   bool is_dropped_frame;
diff --git a/av1/encoder/encoder_alloc.h b/av1/encoder/encoder_alloc.h
index 5daf50b..cac6bee 100644
--- a/av1/encoder/encoder_alloc.h
+++ b/av1/encoder/encoder_alloc.h
@@ -289,6 +289,9 @@
 
   aom_free(cpi->mb_weber_stats);
   cpi->mb_weber_stats = NULL;
+
+  aom_free(cpi->mb_variance);
+  cpi->mb_variance = NULL;
 }
 
 static AOM_INLINE void variance_partition_alloc(AV1_COMP *cpi) {
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index 093311d..dac41de 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -746,7 +746,8 @@
   // Force 64x64 superblock size to increase resolution in perceptual
   // AQ mode.
   if (oxcf->mode == ALLINTRA &&
-      oxcf->q_cfg.deltaq_mode == DELTA_Q_PERCEPTUAL_AI)
+      (oxcf->q_cfg.deltaq_mode == DELTA_Q_PERCEPTUAL_AI ||
+       oxcf->q_cfg.deltaq_mode == DELTA_Q_USER_RATING_BASED))
     return BLOCK_64X64;
 
   assert(oxcf->tool_cfg.superblock_size == AOM_SUPERBLOCK_SIZE_DYNAMIC);