aom: rtc: Skip scene detection for inactive blocks

From 74d1ea7c5151e06bbf83159479bac2da647a9bca Mon Sep 17 00:00:00 2001
From: Marco Paniconi <[EMAIL REDACTED]>
Date: Tue, 12 Mar 2024 14:51:48 -0700
Subject: [PATCH] rtc: Skip scene detection for inactive blocks

For active_maps in real-time mode: the source sad in
the scene detection may be skipped based on inactive
blocks via the active_maps input. This reduces some
calculations, and is bitexact behavior.

Change-Id: I21d53a6fc1da63ecdcdbfb0ebcb9f2c85c4a6870
---
 av1/encoder/encoder.c  |  7 ++++++
 av1/encoder/ratectrl.c | 50 +++++++++++++++++++++++++++++++++++++-----
 av1/encoder/ratectrl.h |  3 +++
 3 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 36ce8ae27f..88862de69d 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -154,14 +154,19 @@ int av1_set_active_map(AV1_COMP *cpi, unsigned char *new_map_16x16, int rows,
     const int mi_rows = mi_params->mi_rows;
     const int mi_cols = mi_params->mi_cols;
     cpi->active_map.update = 0;
+    cpi->rc.percent_blocks_inactive = 0;
     assert(mi_rows % 2 == 0);
     assert(mi_cols % 2 == 0);
     if (new_map_16x16) {
+      int num_samples = 0;
+      int num_blocks_inactive = 0;
       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;
+          num_samples++;
+          if (val == AM_SEGMENT_ID_INACTIVE) num_blocks_inactive++;
           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) {
@@ -173,6 +178,8 @@ int av1_set_active_map(AV1_COMP *cpi, unsigned char *new_map_16x16, int rows,
       }
       cpi->active_map.enabled = 1;
       cpi->active_map.update = 1;
+      cpi->rc.percent_blocks_inactive =
+          (num_blocks_inactive * 100) / num_samples;
     }
     return 0;
   }
diff --git a/av1/encoder/ratectrl.c b/av1/encoder/ratectrl.c
index b7cc06e8e7..7639484df5 100644
--- a/av1/encoder/ratectrl.c
+++ b/av1/encoder/ratectrl.c
@@ -30,6 +30,7 @@
 #include "av1/common/seg_common.h"
 
 #include "av1/encoder/encodemv.h"
+#include "av1/encoder/encoder_utils.h"
 #include "av1/encoder/encode_strategy.h"
 #include "av1/encoder/gop_structure.h"
 #include "av1/encoder/random.h"
@@ -439,6 +440,7 @@ void av1_rc_init(const AV1EncoderConfig *oxcf, RATE_CONTROL *rc) {
   rc->rtc_external_ratectrl = 0;
   rc->frame_level_fast_extra_bits = 0;
   rc->use_external_qp_one_pass = 0;
+  rc->percent_blocks_inactive = 0;
 }
 
 static bool check_buffer_below_thresh(AV1_COMP *cpi, int64_t buffer_level,
@@ -2989,6 +2991,24 @@ void av1_set_rtc_reference_structure_one_layer(AV1_COMP *cpi, int gf_update) {
     cpi->rt_reduce_num_ref_buffers &= (rtc_ref->ref_idx[2] < 7);
 }
 
+static int set_block_is_active(unsigned char *const active_map_4x4, int mi_cols,
+                               int mi_rows, int sbi_col, int sbi_row, int sh,
+                               int num_4x4) {
+  int r = sbi_row << sh;
+  int c = sbi_col << sh;
+  const int row_max = AOMMIN(num_4x4, mi_rows - r);
+  const int col_max = AOMMIN(num_4x4, mi_cols - c);
+  // Active map is set for 16x16 blocks, so only need to
+  // check over16x16,
+  for (int x = 0; x < row_max; x += 4) {
+    for (int y = 0; y < col_max; y += 4) {
+      if (active_map_4x4[(r + x) * mi_cols + (c + y)] == AM_SEGMENT_ID_ACTIVE)
+        return 1;
+    }
+  }
+  return 0;
+}
+
 /*!\brief Check for scene detection, for 1 pass real-time mode.
  *
  * Compute average source sad (temporal sad: between current source and
@@ -3091,11 +3111,26 @@ static void rc_scene_detection_onepass_rt(AV1_COMP *cpi,
                                              sizeof(*cpi->src_sad_blk_64x64)));
     }
   }
+  const CommonModeInfoParams *const mi_params = &cpi->common.mi_params;
+  const int mi_cols = mi_params->mi_cols;
+  const int mi_rows = mi_params->mi_rows;
+  int sh = (cm->seq_params->sb_size == BLOCK_128X128) ? 5 : 4;
+  int num_4x4 = (cm->seq_params->sb_size == BLOCK_128X128) ? 32 : 16;
+  unsigned char *const active_map_4x4 = cpi->active_map.map;
   // Avoid bottom and right border.
   for (int sbi_row = 0; sbi_row < sb_rows - border; ++sbi_row) {
     for (int sbi_col = 0; sbi_col < sb_cols; ++sbi_col) {
-      tmp_sad = cpi->ppi->fn_ptr[bsize].sdf(src_y, src_ystride, last_src_y,
-                                            last_src_ystride);
+      int block_is_active = 1;
+      if (cpi->active_map.enabled && rc->percent_blocks_inactive > 0) {
+        block_is_active = set_block_is_active(active_map_4x4, mi_cols, mi_rows,
+                                              sbi_col, sbi_row, sh, num_4x4);
+      }
+      if (block_is_active) {
+        tmp_sad = cpi->ppi->fn_ptr[bsize].sdf(src_y, src_ystride, last_src_y,
+                                              last_src_ystride);
+      } else {
+        tmp_sad = 0;
+      }
       if (cpi->src_sad_blk_64x64 != NULL)
         cpi->src_sad_blk_64x64[sbi_col + sbi_row * sb_cols] = tmp_sad;
       if (check_light_change) {
@@ -3454,8 +3489,13 @@ void av1_get_one_pass_rt_params(AV1_COMP *cpi, FRAME_TYPE *const frame_type,
       }
     }
   }
-  // Check for scene change: for SVC check on base spatial layer only.
-  if (cpi->sf.rt_sf.check_scene_detection && svc->spatial_layer_id == 0) {
+  if (cpi->active_map.enabled && cpi->rc.percent_blocks_inactive == 100) {
+    rc->frame_source_sad = 0;
+    rc->avg_source_sad = (3 * rc->avg_source_sad + rc->frame_source_sad) >> 2;
+    rc->percent_blocks_with_motion = 0;
+    rc->high_source_sad = 0;
+  } else if (cpi->sf.rt_sf.check_scene_detection &&
+             svc->spatial_layer_id == 0) {
     if (rc->prev_coded_width == cm->width &&
         rc->prev_coded_height == cm->height) {
       rc_scene_detection_onepass_rt(cpi, frame_input);
@@ -3602,4 +3642,4 @@ int av1_encodedframe_overshoot_cbr(AV1_COMP *cpi, int *q) {
     }
   }
   return 1;
-}
\ No newline at end of file
+}
diff --git a/av1/encoder/ratectrl.h b/av1/encoder/ratectrl.h
index 6802ad42d0..0dd8e32b78 100644
--- a/av1/encoder/ratectrl.h
+++ b/av1/encoder/ratectrl.h
@@ -249,6 +249,9 @@ typedef struct {
   // signals if number of blocks with motion is high
   int percent_blocks_with_motion;
 
+  // signals percentge of 16x16 blocks that are inactive, via active_maps
+  int percent_blocks_inactive;
+
   // Maximum value of source sad across all blocks of frame.
   uint64_t max_block_source_sad;