Commit 90f1e4283b for aom

commit 90f1e4283b16f72d6f8c248a794351f248a3288e
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date:   Wed Oct 7 08:41:48 2026 +0200

    rtc: Clear cyclic refresh q deltas when it is off

    When cyclic refresh is off and an active map is set,
    av1_cyclic_refresh_setup() keeps segmentation enabled for the inactive
    segment. It leaves the ALT_Q deltas of the boost segments in place and
    doesn't set update_data, so every frame copies the old deltas from its
    primary reference frame.

    Rate control keeps lowering q while the deltas stay the same. Once
    base q drops to the size of the delta, the boost segments reach
    qindex 0 and become lossless. Nonrd doesn't support lossless segments,
    so the encoder hits the tx size assert in encode_block_inter() or
    writes a stream the decoder rejects or decodes differently.

    Disable the deltas when cyclic refresh is off and signal the change.
    Check the primary reference frame too. It may not be the last encoded
    frame, for example when a frame doesn't refresh LAST.

    Add InactiveAreaTest.ActiveMapSetOnce. It sets a small active map once
    and lets q drop. Without this change it hits the assert at speeds 9 to 11
    with aq 3, and crashes in builds without asserts.

    Change-Id: I42cab25f25017851c3b941025c61c49ca1ffd3a0

diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c
index 52d3aa5509..d87ceae039 100644
--- a/av1/encoder/aq_cyclicrefresh.c
+++ b/av1/encoder/aq_cyclicrefresh.c
@@ -584,6 +584,16 @@ void av1_cyclic_refresh_setup(AV1_COMP *const cpi) {
       unsigned char *const seg_map = cpi->enc_seg.map;
       memset(seg_map, 0, cm->mi_params.mi_rows * cm->mi_params.mi_cols);
       av1_disable_segmentation(&cm->seg);
+    } else if (segfeature_active(seg, CR_SEGMENT_ID_BOOST1, SEG_LVL_ALT_Q) ||
+               segfeature_active(seg, CR_SEGMENT_ID_BOOST2, SEG_LVL_ALT_Q) ||
+               (cm->prev_frame &&
+                (segfeature_active(&cm->prev_frame->seg, CR_SEGMENT_ID_BOOST1,
+                                   SEG_LVL_ALT_Q) ||
+                 segfeature_active(&cm->prev_frame->seg, CR_SEGMENT_ID_BOOST2,
+                                   SEG_LVL_ALT_Q)))) {
+      av1_disable_segfeature(seg, CR_SEGMENT_ID_BOOST1, SEG_LVL_ALT_Q);
+      av1_disable_segfeature(seg, CR_SEGMENT_ID_BOOST2, SEG_LVL_ALT_Q);
+      seg->update_data = 1;
     }
     if (frame_is_intra_only(cm) || scene_change_detected ||
         cpi->ppi->rtc_ref.bias_recovery_frame) {
diff --git a/test/active_map_test.cc b/test/active_map_test.cc
index 5a4ea3496d..47fcb19280 100644
--- a/test/active_map_test.cc
+++ b/test/active_map_test.cc
@@ -147,9 +147,26 @@ void BuildActiveMap(int frame, uint8_t *map) {
   }
 }

+// Marks the top half of the window active, 32 of the 384 blocks. With this
+// share cyclic refresh starts on and turns off a few frames later.
+void BuildSmallActiveMap(uint8_t *map) {
+  memset(map, 0, kMapRows * kMapCols);
+  int win_x;
+  int win_y;
+  WindowPos(1, &win_x, &win_y);
+  for (int r = win_y / 16; r < win_y / 16 + 4; ++r) {
+    for (int c = win_x / 16; c < (win_x + kWinSize) / 16; ++c) {
+      map[r * kMapCols + c] = 1;
+    }
+  }
+}
+
+// With fixed_window the window stays where it is on frame 1 and scrolls one
+// line per frame.
 class ScrollingWindowSource : public ::libaom_test::DummyVideoSource {
  public:
-  ScrollingWindowSource() : doc_(kWinSize * kDocHeight) {
+  explicit ScrollingWindowSource(bool fixed_window)
+      : fixed_window_(fixed_window), doc_(kWinSize * kDocHeight) {
     SetSize(kSrcWidth, kSrcHeight);
     set_limit(kNumFrames);
     ::libaom_test::ACMRandom rnd(0x5eed);
@@ -164,6 +181,7 @@ class ScrollingWindowSource : public ::libaom_test::DummyVideoSource {
  protected:
   void FillFrame() override {
     const int frame = static_cast<int>(frame_);
+    const int layout_frame = fixed_window_ ? 1 : frame;
     uint8_t *const y_plane = img_->planes[AOM_PLANE_Y];
     const int y_stride = img_->stride[AOM_PLANE_Y];
     for (int y = 0; y < kSrcHeight; ++y) {
@@ -181,8 +199,9 @@ class ScrollingWindowSource : public ::libaom_test::DummyVideoSource {

     int win_x;
     int win_y;
-    WindowPos(frame, &win_x, &win_y);
-    const int scroll = ScrollPos(frame);
+    WindowPos(layout_frame, &win_x, &win_y);
+    const int scroll =
+        fixed_window_ ? frame % (kDocHeight - kWinSize) : ScrollPos(frame);
     for (int y = 0; y < kWinSize; ++y) {
       memcpy(y_plane + (win_y + y) * y_stride + win_x,
              &doc_[(scroll + y) * kWinSize], kWinSize);
@@ -191,7 +210,7 @@ class ScrollingWindowSource : public ::libaom_test::DummyVideoSource {
     if (frame == 0) return;

     uint8_t map[kMapRows * kMapCols];
-    BuildActiveMap(frame, map);
+    BuildActiveMap(layout_frame, map);
     ::libaom_test::ACMRandom rnd(frame);
     for (int y = 0; y < kSrcHeight; ++y) {
       for (int x = 0; x < kSrcWidth; ++x) {
@@ -202,6 +221,7 @@ class ScrollingWindowSource : public ::libaom_test::DummyVideoSource {
   }

  private:
+  bool fixed_window_;
   std::vector<uint8_t> doc_;
 };

@@ -244,9 +264,10 @@ class InactiveAreaTest
       encoder->Control(AV1E_SET_ROW_MT, cfg_.g_threads > 1);
       return;
     }
+    if (map_once_ && frame > 1) return;

     uint8_t map[kMapRows * kMapCols];
-    BuildActiveMap(frame, map);
+    BuildMap(frame, map);
     if (use_roi_) {
       const int mi_cols = kSrcWidth / 4;
       const int mi_rows = kSrcHeight / 4;
@@ -278,7 +299,7 @@ class InactiveAreaTest
     const int frame = static_cast<int>(pts);
     if (frame > 0) {
       uint8_t map[kMapRows * kMapCols];
-      BuildActiveMap(frame, map);
+      BuildMap(frame, map);
       std::vector<uint8_t> near_active(kSrcWidth * kSrcHeight, 0);
       for (int r = 0; r < kMapRows; ++r) {
         for (int c = 0; c < kMapCols; ++c) {
@@ -322,15 +343,27 @@ class InactiveAreaTest
     }
   }

-  void DoTest(bool use_roi) {
+  void BuildMap(int frame, uint8_t *map) const {
+    if (map_once_) {
+      BuildSmallActiveMap(map);
+    } else {
+      BuildActiveMap(frame, map);
+    }
+  }
+
+  void DoTest(bool use_roi, bool map_once = false) {
     use_roi_ = use_roi;
-    ScrollingWindowSource video;
+    map_once_ = map_once;
+    // More bits let q drop low enough while the map stays set.
+    if (map_once) cfg_.rc_target_bitrate = 1000;
+    ScrollingWindowSource video(map_once);
     ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   }

   int cpu_used_;
   int aq_mode_;
   bool use_roi_ = false;
+  bool map_once_ = false;
   std::vector<uint8_t> prev_[3];
 };

@@ -338,6 +371,9 @@ TEST_P(InactiveAreaTest, ActiveMap) { DoTest(false); }

 TEST_P(InactiveAreaTest, RoiSkip) { DoTest(true); }

+// The map stays set while cyclic refresh turns off and q drops.
+TEST_P(InactiveAreaTest, ActiveMapSetOnce) { DoTest(false, true); }
+
 AV1_INSTANTIATE_TEST_SUITE(InactiveAreaTest,
                            ::testing::Values(::libaom_test::kRealTime),
                            ::testing::Range(7, 12), ::testing::Values(0, 3),