aom: rtc: Fixes for active_maps for rtc encoder

From 9bc3992b1139371263a5a6339f1477bfdee2bf71 Mon Sep 17 00:00:00 2001
From: Marco Paniconi <[EMAIL REDACTED]>
Date: Fri, 2 Feb 2024 17:37:07 +0000
Subject: [PATCH] rtc: Fixes for active_maps for rtc encoder

Make changes in rtc encoder to make active_maps
work for nonrd mode, and add example to test active maps
in svc_encoder_rtc.c

The critical fixes are:
1) allowing for cyclic refresh to stay enabled on the
   active segment
2) for blocks with segment_skip set: set the flags
   x->force_zeromv_skip_blk = 1, source_sad_nonrd = kZeroSad
   and enter av1_nonrd_pick_inter_mode(). Avoid the call to
   av1_rd_pick_inter_mode_sb_seg_skip(), which is defined for
   rd pickmode and needs to be modified for nonrd
3) Reset the segment on skip mode for inactive segment

Extend ActiveMapTest to speeds up to 11, and for
AQ_MODE=3 (cyclic_refresh) and screen content.

Change-Id: I6bfe26e14526b6799ab1f138fb347ca63a0d2ae1
---
 av1/encoder/aq_cyclicrefresh.c |  4 +---
 av1/encoder/encodeframe.c      |  2 +-
 av1/encoder/encoder.c          | 20 +++++++++++---------
 av1/encoder/encoder_utils.c    | 10 ++++++++--
 av1/encoder/partition_search.c | 19 +++++++++----------
 examples/svc_encoder_rtc.cc    | 34 ++++++++++++++++++++++++++++++++++
 test/active_map_test.cc        | 18 ++++++++++++++----
 7 files changed, 78 insertions(+), 29 deletions(-)

diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c
index f48ff11e51..d2fc0f7bb7 100644
--- a/av1/encoder/aq_cyclicrefresh.c
+++ b/av1/encoder/aq_cyclicrefresh.c
@@ -423,9 +423,7 @@ void av1_cyclic_refresh_update_parameters(AV1_COMP *const cpi) {
   // function av1_cyclic_reset_segment_skip(). Skipping over
   // 4x4 will therefore have small bdrate loss (~0.2%), so
   // we use it only for speed > 9 for now.
-  // Also if loop-filter deltas is applied via segment, then
-  // we need to set cr->skip_over4x4 = 1.
-  cr->skip_over4x4 = (cpi->oxcf.speed > 9) ? 1 : 0;
+  cr->skip_over4x4 = (cpi->oxcf.speed > 9 && !cpi->active_map.enabled) ? 1 : 0;
 
   // should we enable cyclic refresh on this frame.
   cr->apply_cyclic_refresh = 1;
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 19e59d5d0c..7848409595 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -1236,7 +1236,7 @@ static AOM_INLINE void encode_sb_row(AV1_COMP *cpi, ThreadData *td,
 
     // Grade the temporal variation of the sb, the grade will be used to decide
     // fast mode search strategy for coding blocks
-    grade_source_content_sb(cpi, x, tile_data, mi_row, mi_col);
+    if (!seg_skip) grade_source_content_sb(cpi, x, tile_data, mi_row, mi_col);
 
     // encode the superblock
     if (use_nonrd_mode) {
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 4e5edaa4f9..36ce8ae27f 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -153,24 +153,26 @@ int av1_set_active_map(AV1_COMP *cpi, unsigned char *new_map_16x16, int rows,
     unsigned char *const active_map_4x4 = cpi->active_map.map;
     const int mi_rows = mi_params->mi_rows;
     const int mi_cols = mi_params->mi_cols;
-    const int row_scale = mi_size_high_log2[BLOCK_16X16];
-    const int col_scale = mi_size_wide_log2[BLOCK_16X16];
     cpi->active_map.update = 0;
     assert(mi_rows % 2 == 0);
     assert(mi_cols % 2 == 0);
     if (new_map_16x16) {
-      for (int r = 0; r < (mi_rows >> row_scale); ++r) {
-        for (int c = 0; c < (mi_cols >> col_scale); ++c) {
-          const uint8_t val = new_map_16x16[r * cols + c]
+      for (int r = 0; r < mi_rows; r += 4) {
+        for (int c = 0; c < mi_cols; c += 4) {
+          const uint8_t val = new_map_16x16[(r >> 2) * cols + (c >> 2)]
                                   ? AM_SEGMENT_ID_ACTIVE
                                   : AM_SEGMENT_ID_INACTIVE;
-          active_map_4x4[(2 * r + 0) * mi_cols + (c + 0)] = val;
-          active_map_4x4[(2 * r + 0) * mi_cols + (c + 1)] = val;
-          active_map_4x4[(2 * r + 1) * mi_cols + (c + 0)] = val;
-          active_map_4x4[(2 * r + 1) * mi_cols + (c + 1)] = val;
+          const int row_max = AOMMIN(4, mi_rows - r);
+          const int col_max = AOMMIN(4, mi_cols - c);
+          for (int x = 0; x < row_max; ++x) {
+            for (int y = 0; y < col_max; ++y) {
+              active_map_4x4[(r + x) * mi_cols + (c + y)] = val;
+            }
+          }
         }
       }
       cpi->active_map.enabled = 1;
+      cpi->active_map.update = 1;
     }
     return 0;
   }
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index d270230539..5bf9f20e53 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -434,8 +434,14 @@ void av1_apply_active_map(AV1_COMP *cpi) {
     if (cpi->active_map.enabled) {
       const int num_mis =
           cpi->common.mi_params.mi_rows * cpi->common.mi_params.mi_cols;
-      for (i = 0; i < num_mis; ++i)
-        if (seg_map[i] == AM_SEGMENT_ID_ACTIVE) seg_map[i] = active_map[i];
+      for (i = 0; i < num_mis; ++i) {
+        // In active region: only unset segmentation map if cyclic refresh is
+        // not set.
+        if (active_map[i] == AM_SEGMENT_ID_INACTIVE ||
+            (seg_map[i] != CR_SEGMENT_ID_BOOST1 &&
+             seg_map[i] != CR_SEGMENT_ID_BOOST2))
+          seg_map[i] = active_map[i];
+      }
       av1_enable_segmentation(seg);
       av1_enable_segfeature(seg, AM_SEGMENT_ID_INACTIVE, SEG_LVL_SKIP);
       av1_enable_segfeature(seg, AM_SEGMENT_ID_INACTIVE, SEG_LVL_ALT_LF_Y_H);
diff --git a/av1/encoder/partition_search.c b/av1/encoder/partition_search.c
index 1c17b09ee1..344141f98d 100644
--- a/av1/encoder/partition_search.c
+++ b/av1/encoder/partition_search.c
@@ -2144,8 +2144,9 @@ static void encode_b_nonrd(const AV1_COMP *const cpi, TileDataEnc *tile_data,
     }
     if (tile_data->allow_update_cdf) update_stats(&cpi->common, td);
   }
-  if (cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ && mbmi->skip_txfm &&
-      !cpi->rc.rtc_external_ratectrl && cm->seg.enabled)
+  if ((cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ ||
+       cpi->active_map.enabled) &&
+      mbmi->skip_txfm && !cpi->rc.rtc_external_ratectrl && cm->seg.enabled)
     av1_cyclic_reset_segment_skip(cpi, x, mi_row, mi_col, bsize, dry_run);
   // TODO(Ravi/Remya): Move this copy function to a better logical place
   // This function will copy the best mode information from block
@@ -2306,15 +2307,13 @@ static void pick_sb_modes_nonrd(AV1_COMP *const cpi, TileDataEnc *tile_data,
     start_timing(cpi, nonrd_pick_inter_mode_sb_time);
 #endif
     if (segfeature_active(&cm->seg, mbmi->segment_id, SEG_LVL_SKIP)) {
-      RD_STATS invalid_rd;
-      av1_invalid_rd_stats(&invalid_rd);
-      // TODO(kyslov): add av1_nonrd_pick_inter_mode_sb_seg_skip
-      av1_rd_pick_inter_mode_sb_seg_skip(cpi, tile_data, x, mi_row, mi_col,
-                                         rd_cost, bsize, ctx,
-                                         invalid_rd.rdcost);
-    } else {
-      av1_nonrd_pick_inter_mode_sb(cpi, tile_data, x, rd_cost, bsize, ctx);
+      x->force_zeromv_skip_for_blk = 1;
+      x->content_state_sb.source_sad_nonrd = kZeroSad;
+      // TODO(marpan): Consider adding a function for nonrd:
+      // av1_nonrd_pick_inter_mode_sb_seg_skip(), instead of setting
+      // x->force_zeromv_skip flag and entering av1_nonrd_pick_inter_mode_sb().
     }
+    av1_nonrd_pick_inter_mode_sb(cpi, tile_data, x, rd_cost, bsize, ctx);
 #if CONFIG_COLLECT_COMPONENT_TIMING
     end_timing(cpi, nonrd_pick_inter_mode_sb_time);
 #endif
diff --git a/examples/svc_encoder_rtc.cc b/examples/svc_encoder_rtc.cc
index 2c041081e5..c751e9868c 100644
--- a/examples/svc_encoder_rtc.cc
+++ b/examples/svc_encoder_rtc.cc
@@ -1442,6 +1442,35 @@ static int qindex_to_quantizer(int qindex) {
   return 63;
 }
 
+static void set_active_map(const aom_codec_enc_cfg_t *cfg,
+                           aom_codec_ctx_t *codec, int frame_cnt) {
+  aom_active_map_t map = { 0, 0, 0 };
+
+  map.rows = (cfg->g_h + 15) / 16;
+  map.cols = (cfg->g_w + 15) / 16;
+
+  map.active_map = (uint8_t *)malloc(map.rows * map.cols);
+  if (!map.active_map) die("Failed to allocate active map");
+
+  // Example map for testing.
+  for (unsigned int i = 0; i < map.rows; ++i) {
+    for (unsigned int j = 0; j < map.cols; ++j) {
+      int index = map.cols * i + j;
+      map.active_map[index] = 1;
+      if (frame_cnt < 300) {
+        if (i < map.rows / 2 && j < map.cols / 2) map.active_map[index] = 0;
+      } else if (frame_cnt >= 300) {
+        if (i < map.rows / 2 && j >= map.cols / 2) map.active_map[index] = 0;
+      }
+    }
+  }
+
+  if (aom_codec_control(codec, AOME_SET_ACTIVEMAP, &map))
+    die_codec(codec, "Failed to set active map");
+
+  free(map.active_map);
+}
+
 int main(int argc, const char **argv) {
   AppInput app_input;
   AvxVideoWriter *outfile[AOM_MAX_LAYERS] = { NULL };
@@ -1494,6 +1523,9 @@ int main(int argc, const char **argv) {
   // Flag to test setting speed per layer.
   const int test_speed_per_layer = 0;
 
+  // Flag for testing active maps.
+  const int test_active_maps = 0;
+
   /* Setup default input stream settings */
   app_input.input_ctx.framerate.numerator = 30;
   app_input.input_ctx.framerate.denominator = 1;
@@ -1874,6 +1906,8 @@ int main(int argc, const char **argv) {
         }
       }
 
+      if (test_active_maps) set_active_map(&cfg, &codec, frame_cnt);
+
       // Do the layer encode.
       aom_usec_timer_start(&timer);
       if (aom_codec_encode(&codec, frame_avail ? &raw : NULL, pts, 1, flags))
diff --git a/test/active_map_test.cc b/test/active_map_test.cc
index 979ee6b8b3..de16541281 100644
--- a/test/active_map_test.cc
+++ b/test/active_map_test.cc
@@ -19,8 +19,10 @@
 
 namespace {
 
+// Params: test mode, speed, aq_mode and screen_content mode.
 class ActiveMapTest
-    : public ::libaom_test::CodecTestWith2Params<libaom_test::TestMode, int>,
+    : public ::libaom_test::CodecTestWith4Params<libaom_test::TestMode, int,
+                                                 int, int>,
       public ::libaom_test::EncoderTest {
  protected:
   static const int kWidth = 208;
@@ -32,6 +34,8 @@ class ActiveMapTest
   void SetUp() override {
     InitializeConfig(GET_PARAM(1));
     cpu_used_ = GET_PARAM(2);
+    aq_mode_ = GET_PARAM(3);
+    screen_mode_ = GET_PARAM(4);
   }
 
   void PreEncodeFrameHook(::libaom_test::VideoSource *video,
@@ -41,6 +45,9 @@ class ActiveMapTest
       encoder->Control(AV1E_SET_ALLOW_WARPED_MOTION, 0);
       encoder->Control(AV1E_SET_ENABLE_GLOBAL_MOTION, 0);
       encoder->Control(AV1E_SET_ENABLE_OBMC, 0);
+      encoder->Control(AV1E_SET_AQ_MODE, aq_mode_);
+      encoder->Control(AV1E_SET_TUNE_CONTENT, screen_mode_);
+      if (screen_mode_) encoder->Control(AV1E_SET_ENABLE_PALETTE, 1);
     } else if (video->frame() == 3) {
       aom_active_map_t map = aom_active_map_t();
       /* clang-format off */
@@ -79,19 +86,22 @@ class ActiveMapTest
     cfg_.g_pass = AOM_RC_ONE_PASS;
     cfg_.rc_end_usage = AOM_CBR;
     cfg_.kf_max_dist = 90000;
-    ::libaom_test::I420VideoSource video("hantro_odd.yuv", kWidth, kHeight, 30,
-                                         1, 0, 20);
+    ::libaom_test::I420VideoSource video("hantro_odd.yuv", kWidth, kHeight, 100,
+                                         1, 0, 100);
 
     ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   }
 
   int cpu_used_;
+  int aq_mode_;
+  int screen_mode_;
 };
 
 TEST_P(ActiveMapTest, Test) { DoTest(); }
 
 AV1_INSTANTIATE_TEST_SUITE(ActiveMapTest,
                            ::testing::Values(::libaom_test::kRealTime),
-                           ::testing::Range(5, 9));
+                           ::testing::Range(5, 12), ::testing::Values(0, 3),
+                           ::testing::Values(0, 1));
 
 }  // namespace