Commit 337454c2c4 for aom

commit 337454c2c41b0878a6ce24c53c03eb26592a624f
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date:   Tue Oct 13 11:11:35 2026 +0200

    rtc: Fix the map returned by AV1E_GET_ACTIVEMAP

    av1_get_active_map() kept the VP9 layout. It walked mi_rows >> 2 by
    mi_cols >> 2 blocks but read enc_seg.map at mi (2 * r, 2 * c), which is
    the VP9 8x8 mi grid. In AV1 a 16x16 block is 4x4 mi, so each block was
    read from the wrong place. The loop bounds also round down, so when the
    frame ends halfway through a 16x16 block (mi_cols or mi_rows % 4 == 2)
    the last column or row was never read and kept its memset value.

    enc_seg.map is also the wrong source. av1_apply_active_map() only copies
    the map into it at encode time, so a get right after a set returns old
    content (all active before the first frame). With aq 3, cyclic refresh
    writes the coded segment ids back into it, so after a frame it no longer
    matches the map that was set.

    Read cpi->active_map.map instead, with the same indexing as
    av1_set_active_map(). The getter now returns the map the application
    set, not the segment map of the last frame. Every mi of a 16x16 block
    holds the same value there, so the top left mi is enough. When the map
    is off, for example after a key frame, all blocks still read as active.

    Add GetActiveMapTest. It sets an asymmetric map on a 200x136 frame, so
    the last column and row are partial, mirrors it on odd frames, and
    checks the get after each set and after each inter frame.

    Repro before this change: 208x144 or 200x136, speed 9, aq 0 or 3, the
    left 6 of 13 block columns inactive plus a few blocks on the last row
    and column. Every get returned 23 to 61 of 117 blocks wrong, in both a
    default and a CONFIG_REALTIME_ONLY build.

    Change-Id: Ic1ed075396e506d62cb697e980770560bbce3624

diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 5a606d304a..2702a2abc6 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -269,30 +269,19 @@ int av1_get_active_map(AV1_COMP *cpi, unsigned char *new_map_16x16, int rows,
   const CommonModeInfoParams *const mi_params = &cpi->common.mi_params;
   if (rows == mi_params->mb_rows && cols == mi_params->mb_cols &&
       new_map_16x16) {
-    unsigned char *const seg_map_8x8 = cpi->enc_seg.map;
+    const 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];
     assert(mi_rows % 2 == 0);
     assert(mi_cols % 2 == 0);

     memset(new_map_16x16, !cpi->active_map.enabled, rows * cols);
     if (cpi->active_map.enabled) {
-      for (int r = 0; r < (mi_rows >> row_scale); ++r) {
-        for (int c = 0; c < (mi_cols >> col_scale); ++c) {
-          // Cyclic refresh segments are considered active despite not having
-          // AM_SEGMENT_ID_ACTIVE
-          uint8_t temp = 0;
-          temp |= seg_map_8x8[(2 * r + 0) * mi_cols + (2 * c + 0)] !=
-                  AM_SEGMENT_ID_INACTIVE;
-          temp |= seg_map_8x8[(2 * r + 0) * mi_cols + (2 * c + 1)] !=
-                  AM_SEGMENT_ID_INACTIVE;
-          temp |= seg_map_8x8[(2 * r + 1) * mi_cols + (2 * c + 0)] !=
-                  AM_SEGMENT_ID_INACTIVE;
-          temp |= seg_map_8x8[(2 * r + 1) * mi_cols + (2 * c + 1)] !=
-                  AM_SEGMENT_ID_INACTIVE;
-          new_map_16x16[r * cols + c] |= temp;
+      // enc_seg.map holds the coded segment ids, so read the map as set.
+      for (int r = 0; r < mi_rows; r += 4) {
+        for (int c = 0; c < mi_cols; c += 4) {
+          new_map_16x16[(r >> 2) * cols + (c >> 2)] =
+              active_map_4x4[r * mi_cols + c] != AM_SEGMENT_ID_INACTIVE;
         }
       }
     }
diff --git a/test/active_map_test.cc b/test/active_map_test.cc
index be3d8d26fd..b3beb7a277 100644
--- a/test/active_map_test.cc
+++ b/test/active_map_test.cc
@@ -214,6 +214,120 @@ AV1_INSTANTIATE_TEST_SUITE(ActiveMapSvcTest,
                            ::testing::Values(::libaom_test::kRealTime),
                            ::testing::Range(7, 12), ::testing::Values(1, 2));

+class MovingGradientSource : public ::libaom_test::DummyVideoSource {
+ protected:
+  void FillFrame() override {
+    memset(img_->img_data, 128, raw_sz_);
+    for (unsigned int y = 0; y < img_->d_h; ++y) {
+      for (unsigned int x = 0; x < img_->d_w; ++x) {
+        img_->planes[AOM_PLANE_Y][y * img_->stride[AOM_PLANE_Y] + x] =
+            static_cast<uint8_t>(x * 3 + y + frame_ * 7);
+      }
+    }
+  }
+};
+
+// Params: test mode, speed and aq_mode.
+class GetActiveMapTest
+    : public ::libaom_test::CodecTestWith3Params<libaom_test::TestMode, int,
+                                                 int>,
+      public ::libaom_test::EncoderTest {
+ protected:
+  // The last 16x16 column and row only cover 8 pixels.
+  static const int kWidth = 200;
+  static const int kHeight = 136;
+  static const int kCols = 13;
+  static const int kRows = 9;
+
+  GetActiveMapTest() : EncoderTest(GET_PARAM(0)) {}
+  ~GetActiveMapTest() override = default;
+
+  void SetUp() override {
+    InitializeConfig(GET_PARAM(1));
+    cpu_used_ = GET_PARAM(2);
+    aq_mode_ = GET_PARAM(3);
+  }
+
+  // Mirrors the pattern on odd frames so a stale map does not match.
+  void BuildMap(int frame, uint8_t *map) const {
+    /* clang-format off */
+    static const uint8_t kMap[kRows * kCols] = {
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
+      0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+      0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
+    };
+    /* clang-format on */
+    for (int r = 0; r < kRows; ++r) {
+      for (int c = 0; c < kCols; ++c) {
+        const int src_c = (frame & 1) ? kCols - 1 - c : c;
+        map[r * kCols + c] = kMap[r * kCols + src_c];
+      }
+    }
+  }
+
+  void ExpectMap(::libaom_test::Encoder *encoder, const uint8_t *expected,
+                 int frame) {
+    uint8_t map_out[kRows * kCols];
+    memset(map_out, 2, sizeof(map_out));
+    aom_active_map_t map = aom_active_map_t();
+    map.rows = kRows;
+    map.cols = kCols;
+    map.active_map = map_out;
+    encoder->Control(AV1E_GET_ACTIVEMAP, &map);
+    for (int i = 0; i < kRows * kCols; ++i) {
+      ASSERT_EQ(map_out[i], expected[i])
+          << "frame " << frame << " row " << i / kCols << " col " << i % kCols;
+    }
+  }
+
+  void PreEncodeFrameHook(::libaom_test::VideoSource *video,
+                          ::libaom_test::Encoder *encoder) override {
+    const int frame = static_cast<int>(video->frame());
+    if (frame == 0) {
+      encoder->Control(AOME_SET_CPUUSED, cpu_used_);
+      encoder->Control(AV1E_SET_AQ_MODE, aq_mode_);
+    }
+    uint8_t map_in[kRows * kCols];
+    // The key frame turns the map off, so check from the second inter frame.
+    if (frame >= 2) {
+      BuildMap(frame - 1, map_in);
+      ASSERT_NO_FATAL_FAILURE(ExpectMap(encoder, map_in, frame));
+    }
+    BuildMap(frame, map_in);
+    aom_active_map_t map = aom_active_map_t();
+    map.rows = kRows;
+    map.cols = kCols;
+    map.active_map = map_in;
+    encoder->Control(AOME_SET_ACTIVEMAP, &map);
+    ASSERT_NO_FATAL_FAILURE(ExpectMap(encoder, map_in, frame));
+  }
+
+  int cpu_used_;
+  int aq_mode_;
+};
+
+TEST_P(GetActiveMapTest, GetMatchesSet) {
+  cfg_.g_lag_in_frames = 0;
+  cfg_.rc_target_bitrate = 400;
+  cfg_.rc_end_usage = AOM_CBR;
+  cfg_.g_pass = AOM_RC_ONE_PASS;
+  cfg_.kf_max_dist = 90000;
+  MovingGradientSource video;
+  video.SetSize(kWidth, kHeight);
+  video.set_limit(10);
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
+AV1_INSTANTIATE_TEST_SUITE(GetActiveMapTest,
+                           ::testing::Values(::libaom_test::kRealTime),
+                           ::testing::Range(7, 12), ::testing::Values(0, 3));
+
 constexpr int kSrcWidth = 384;
 constexpr int kSrcHeight = 256;
 constexpr int kMapCols = kSrcWidth / 16;