rtc: Clamp ROI segment q against base_qindex

av1_apply_roi_map() checked each segment's delta q against the qindex
of the last segment that had a delta, not against base_qindex. So a
segment after a raised one could skip the clamp, or get a clamp value
from the wrong qindex, and end up at qindex 0. With base_qindex 0, a
segment without a delta stayed at qindex 0 once an earlier segment had
been moved to 1. nonrd can't code a lossless segment. It hits the
lossless tx size assert in av1_nonrd_pick_intra_mode(), and without
asserts the stream is broken.

Clamp every segment against base_qindex. The ROI rdmult still follows
the last segment with a requested delta, so moving a segment off
qindex 0 does not change it.

Repro: ROIMapTest.ROIDeltaQMixed sets segment 1 to +10 and segment 2
to -63, which makes segment 2 lossless on every frame.

Change-Id: I5a7d43356f5620edaf3587b4dbd6af31a7396c4b
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index 3d995ba..7a478da 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -436,7 +436,10 @@
   int ref_frame[8];
   int internal_delta_q[MAX_SEGMENTS];
   int skip_enabled = 0;
-  int qindex = cm->quant_params.base_qindex;
+  const int base_qindex = cm->quant_params.base_qindex;
+  // Qindex of the last segment with a requested delta q, used for the ROI
+  // rdmult.
+  int roi_qindex = base_qindex;
 
   // Force disable of ROI if active_map is enabled. ROI for now
   // only supported/tested for realtime mode with speed >= 7.
@@ -485,17 +488,16 @@
     if (delta_q[i] < 0) internal_delta_q[i] = -internal_delta_q[i];
     // Clamp to allowed best/worst quality. For best quality clamp to 1
     // to avoid qindex = 0 case (lossless segment) which can happen for
-    // best_quality = 0.
-    if (qindex + internal_delta_q[i] <= cpi->rc.best_quality)
-      internal_delta_q[i] = AOMMAX(1, cpi->rc.best_quality) - qindex;
-    if (qindex + internal_delta_q[i] > cpi->rc.worst_quality)
-      internal_delta_q[i] =
-          cpi->rc.worst_quality - cm->quant_params.base_qindex;
+    // best_quality = 0. The segment qindex is base_qindex plus its delta.
+    if (base_qindex + internal_delta_q[i] <= cpi->rc.best_quality)
+      internal_delta_q[i] = AOMMAX(1, cpi->rc.best_quality) - base_qindex;
+    if (base_qindex + internal_delta_q[i] > cpi->rc.worst_quality)
+      internal_delta_q[i] = cpi->rc.worst_quality - base_qindex;
     if (internal_delta_q[i] != 0) {
       av1_enable_segfeature(seg, i, SEG_LVL_ALT_Q);
       av1_set_segdata(seg, i, SEG_LVL_ALT_Q, internal_delta_q[i]);
       roi->delta_qp_enabled = 1;
-      qindex = cm->quant_params.base_qindex + internal_delta_q[i];
+      if (delta_q[i] != 0) roi_qindex = base_qindex + internal_delta_q[i];
     }
     if (delta_lf[i] != 0) {
       // Disable loopfilter delta from ROI, as it requires
@@ -536,7 +538,7 @@
     roi->enabled = 1;
     if (roi->delta_qp_enabled) {
       roi->rdmult_delta_qp = av1_compute_rd_mult(
-          qindex, cm->seq_params->bit_depth,
+          roi_qindex, cm->seq_params->bit_depth,
           cpi->ppi->gf_group.update_type[cpi->gf_frame_index], 0, 15,
           INTER_FRAME, cpi->oxcf.q_cfg.use_fixed_qp_offsets,
           is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning,
diff --git a/test/roi_map_test.cc b/test/roi_map_test.cc
index 4da3c95..627076f 100644
--- a/test/roi_map_test.cc
+++ b/test/roi_map_test.cc
@@ -27,7 +27,7 @@
 
 namespace {
 
-enum { kSkip = 0, kDeltaQ = 1, kDeltaLF = 2, kReference = 3 };
+enum { kSkip = 0, kDeltaQ = 1, kDeltaLF = 2, kReference = 3, kDeltaQMixed = 4 };
 
 // Params: test mode, speed, aq_mode and screen_content mode.
 class ROIMapTest
@@ -80,6 +80,8 @@
           if (i > roi.rows / 4 && i < (3 * roi.rows) / 4 && j > roi.cols / 4 &&
               j < (3 * roi.cols) / 4)
             roi.roi_map[idx] = 1;
+          else if (roi_feature_ == kDeltaQMixed && i < roi.rows / 4)
+            roi.roi_map[idx] = 2;
           else
             roi.roi_map[idx] = 0;
         }
@@ -91,6 +93,11 @@
         roi.delta_q[1] = -40;
       else if (roi_feature_ == kReference)
         roi.ref_frame[1] = 4;  // GOLDEN_FRAME;
+      else if (roi_feature_ == kDeltaQMixed) {
+        // Segment 2 must not end up lossless after segment 1 raised the q.
+        roi.delta_q[1] = 10;
+        roi.delta_q[2] = -63;
+      }
       encoder->Control(AOME_SET_ROI_MAP, &roi);
       free(roi.roi_map);
     }
@@ -128,6 +135,22 @@
     ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   }
 
+  void ROIDeltaQMixedTest() {
+    cfg_.g_lag_in_frames = 0;
+    cfg_.rc_target_bitrate = 400;
+    cfg_.rc_resize_mode = 0;
+    cfg_.g_pass = AOM_RC_ONE_PASS;
+    cfg_.rc_end_usage = AOM_CBR;
+    cfg_.kf_max_dist = 90000;
+    cfg_.rc_min_quantizer = 0;
+    cfg_.rc_max_quantizer = 40;
+    roi_feature_ = kDeltaQMixed;
+    ::libaom_test::I420VideoSource video("niklas_640_480_30.yuv", 640, 480, 30,
+                                         1, 0, 30);
+
+    ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  }
+
   void ROIReferenceTest() {
     cfg_.g_lag_in_frames = 0;
     cfg_.rc_target_bitrate = 400;
@@ -158,6 +181,8 @@
 
 TEST_P(ROIMapTest, ROIReference) { ROIReferenceTest(); }
 
+TEST_P(ROIMapTest, ROIDeltaQMixed) { ROIDeltaQMixedTest(); }
+
 AV1_INSTANTIATE_TEST_SUITE(ROIMapTest,
                            ::testing::Values(::libaom_test::kRealTime),
                            ::testing::Range(7, 12), ::testing::Values(0, 1),