Commit 64336ec42b for aom
commit 64336ec42bfad8fc8fc7c1d40f472bc0c97060f7
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Thu Oct 22 08:54:43 2026 +0200
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 3d995baca6..7a478da97d 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -436,7 +436,10 @@ void av1_apply_roi_map(AV1_COMP *cpi) {
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 @@ void av1_apply_roi_map(AV1_COMP *cpi) {
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 @@ void av1_apply_roi_map(AV1_COMP *cpi) {
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 4da3c95c42..627076f945 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 @@ class ROIMapTest
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 @@ class ROIMapTest
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 @@ class ROIMapTest
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, ROIDeltaQ) { ROIDeltaQTest(); }
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),