Commit 3712e6af6f for aom
commit 3712e6af6f142785a2059e2fc03d73ac76c62f80
Author: Mudassir Galaganath <mudassir.galaganath@ittiam.com>
Date: Thu Jun 4 14:24:46 2026 +0530
Fix mode_ref_delta_update signaling when delta is disabled
When mode_ref_delta_enabled is false (0), the encoder was
unconditionally writing the mode_ref_delta_update flag to the
bitstream. However, standard AV1 decoders (following Section
5.9.11 of the specification) only parse the update flag and
delta lists if loop_filter_delta_enabled is true.
This patch fixes the issue by nesting the delta update signaling
inside the mode_ref_delta_enabled check in encode_loopfilter(),
matching the parsing logic of the decoder. To validate this,
a codec control API (AV1E_SET_MODE_REF_DELTA_ENABLED) is added to
the encoder and 'LFControlModeRefDeltaEndToEndTestTest' unit test is
added in loopfilter_control_test.cc using the exisiting framework.
Change-Id: I3fa91f6c6d7eb818f3ea164fb950690aefa37891
diff --git a/aom/aomcx.h b/aom/aomcx.h
index 0e33a398c7..b1a4bed8b7 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1647,6 +1647,15 @@ enum aome_enc_control_id {
*/
AOME_SET_VALIDATE_HBD_INPUT = 175,
+ /*!\brief Codec control function to toggle loopfilter mode_ref_delta_enabled.
+ *
+ * - 0 = disable
+ * - 1 = enable (default)
+ *
+ * \note This is only used in loopfilter control unit test.
+ */
+ AV1E_SET_MODE_REF_DELTA_ENABLED = 176,
+
// Any new encoder control IDs should be added above.
// Maximum allowed encoder control ID is 229.
// No encoder control ID should be added below.
@@ -2433,6 +2442,9 @@ AOM_CTRL_USE_TYPE(AV1E_SET_EXTERNAL_RATE_CONTROL, aom_rc_funcs_t *)
AOM_CTRL_USE_TYPE(AV1E_GET_GOP_INFO, aom_gop_info_t *)
#define AOM_CTRL_AV1E_GET_GOP_INFO
+AOM_CTRL_USE_TYPE(AV1E_SET_MODE_REF_DELTA_ENABLED, int)
+#define AOM_CTRL_AV1E_SET_MODE_REF_DELTA_ENABLED
+
/*!\endcond */
/*! @} - end defgroup aom_encoder */
#ifdef __cplusplus
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 9e921bd240..ba59e9e21d 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -227,6 +227,7 @@ struct av1_extracfg {
// Indicates if the application of post-processing filters should be skipped
// on reconstructed frame.
unsigned int skip_postproc_filtering;
+ int mode_ref_delta_enabled;
// the name of the second pass output file when passes > 2
const char *two_pass_output;
const char *second_pass_log;
@@ -397,6 +398,7 @@ static const struct av1_extracfg default_extra_cfg = {
-1, // fwd_kf_dist
LOOPFILTER_ALL, // loopfilter_control
0, // skip_postproc_filtering
+ 1, // mode_ref_delta_enabled
NULL, // two_pass_output
NULL, // second_pass_log
0, // auto_intra_tools_off
@@ -555,6 +557,7 @@ static const struct av1_extracfg default_extra_cfg = {
-1, // fwd_kf_dist
LOOPFILTER_ALL, // loopfilter_control
0, // skip_postproc_filtering
+ 1, // mode_ref_delta_enabled
NULL, // two_pass_output
NULL, // second_pass_log
0, // auto_intra_tools_off
@@ -946,6 +949,7 @@ static aom_codec_err_t validate_config(aom_codec_alg_priv_t *ctx,
RANGE_CHECK_HI(extra_cfg, deltaq_strength, 1000);
RANGE_CHECK_HI(extra_cfg, loopfilter_control, 3);
RANGE_CHECK_BOOL(extra_cfg, skip_postproc_filtering);
+ RANGE_CHECK_BOOL(extra_cfg, mode_ref_delta_enabled);
RANGE_CHECK_HI(extra_cfg, enable_cdef, 3);
RANGE_CHECK_BOOL(extra_cfg, auto_intra_tools_off);
RANGE_CHECK_BOOL(extra_cfg, strict_level_conformance);
@@ -1366,6 +1370,7 @@ static void set_encoder_config(AV1EncoderConfig *oxcf,
resize_cfg->resize_mode ? 0 : extra_cfg->enable_tpl_model;
algo_cfg->loopfilter_control = extra_cfg->loopfilter_control;
algo_cfg->skip_postproc_filtering = extra_cfg->skip_postproc_filtering;
+ algo_cfg->mode_ref_delta_enabled = extra_cfg->mode_ref_delta_enabled;
algo_cfg->screen_detection_mode = extra_cfg->screen_detection_mode;
// Set two-pass stats configuration.
@@ -2838,6 +2843,14 @@ static aom_codec_err_t ctrl_set_skip_postproc_filtering(
return update_extra_cfg(ctx, &extra_cfg);
}
+static aom_codec_err_t ctrl_set_mode_ref_delta_enabled(
+ aom_codec_alg_priv_t *ctx, va_list args) {
+ struct av1_extracfg extra_cfg = ctx->extra_cfg;
+ extra_cfg.mode_ref_delta_enabled =
+ CAST(AV1E_SET_MODE_REF_DELTA_ENABLED, args);
+ return update_extra_cfg(ctx, &extra_cfg);
+}
+
static aom_codec_err_t ctrl_set_rtc_external_rc(aom_codec_alg_priv_t *ctx,
va_list args) {
ctx->ppi->cpi->rc.rtc_external_ratectrl =
@@ -5086,6 +5099,7 @@ static aom_codec_ctrl_fn_map_t encoder_ctrl_maps[] = {
{ AV1E_SET_ENABLE_TX_SIZE_SEARCH, ctrl_set_enable_tx_size_search },
{ AV1E_SET_LOOPFILTER_CONTROL, ctrl_set_loopfilter_control },
{ AV1E_SET_SKIP_POSTPROC_FILTERING, ctrl_set_skip_postproc_filtering },
+ { AV1E_SET_MODE_REF_DELTA_ENABLED, ctrl_set_mode_ref_delta_enabled },
{ AV1E_SET_AUTO_INTRA_TOOLS_OFF, ctrl_set_auto_intra_tools_off },
{ AV1E_SET_RTC_EXTERNAL_RC, ctrl_set_rtc_external_rc },
{ AV1E_SET_QUANTIZER_ONE_PASS, ctrl_set_quantizer_one_pass },
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index 558ac361e8..53ca4adac1 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -2047,35 +2047,39 @@ static inline void encode_loopfilter(AV1_COMMON *cm,
aom_wb_write_bit(wb, lf->mode_ref_delta_enabled);
- // Write out loop filter deltas applied at the MB level based on mode or
- // ref frame (if they are enabled), only if there is information to write.
- int meaningful = is_mode_ref_delta_meaningful(cm);
- aom_wb_write_bit(wb, meaningful);
- if (!meaningful) {
- return;
- }
+ if (lf->mode_ref_delta_enabled) {
+ // Check if the loop filter deltas have changed from the previous frame.
+ // If mode_ref_delta_update_decision is 1, signal the update and write out
+ // the actual delta values. Otherwise, signal 0 and skip writing delta
+ // values.
+ int mode_ref_delta_update = is_mode_ref_delta_meaningful(cm);
+ aom_wb_write_bit(wb, mode_ref_delta_update);
+ if (!mode_ref_delta_update) {
+ return;
+ }
- const RefCntBuffer *buf = get_primary_ref_frame_buf(cm);
- int8_t last_ref_deltas[REF_FRAMES];
- int8_t last_mode_deltas[MAX_MODE_LF_DELTAS];
- if (buf == NULL) {
- av1_set_default_ref_deltas(last_ref_deltas);
- av1_set_default_mode_deltas(last_mode_deltas);
- } else {
- memcpy(last_ref_deltas, buf->ref_deltas, REF_FRAMES);
- memcpy(last_mode_deltas, buf->mode_deltas, MAX_MODE_LF_DELTAS);
- }
- for (int i = 0; i < REF_FRAMES; i++) {
- const int delta = lf->ref_deltas[i];
- const int changed = delta != last_ref_deltas[i];
- aom_wb_write_bit(wb, changed);
- if (changed) aom_wb_write_inv_signed_literal(wb, delta, 6);
- }
- for (int i = 0; i < MAX_MODE_LF_DELTAS; i++) {
- const int delta = lf->mode_deltas[i];
- const int changed = delta != last_mode_deltas[i];
- aom_wb_write_bit(wb, changed);
- if (changed) aom_wb_write_inv_signed_literal(wb, delta, 6);
+ const RefCntBuffer *buf = get_primary_ref_frame_buf(cm);
+ int8_t last_ref_deltas[REF_FRAMES];
+ int8_t last_mode_deltas[MAX_MODE_LF_DELTAS];
+ if (buf == NULL) {
+ av1_set_default_ref_deltas(last_ref_deltas);
+ av1_set_default_mode_deltas(last_mode_deltas);
+ } else {
+ memcpy(last_ref_deltas, buf->ref_deltas, REF_FRAMES);
+ memcpy(last_mode_deltas, buf->mode_deltas, MAX_MODE_LF_DELTAS);
+ }
+ for (int i = 0; i < REF_FRAMES; i++) {
+ const int delta = lf->ref_deltas[i];
+ const int changed = delta != last_ref_deltas[i];
+ aom_wb_write_bit(wb, changed);
+ if (changed) aom_wb_write_inv_signed_literal(wb, delta, 6);
+ }
+ for (int i = 0; i < MAX_MODE_LF_DELTAS; i++) {
+ const int delta = lf->mode_deltas[i];
+ const int changed = delta != last_mode_deltas[i];
+ aom_wb_write_bit(wb, changed);
+ if (changed) aom_wb_write_inv_signed_literal(wb, delta, 6);
+ }
}
}
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 8170c1d857..1ccff9e631 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -2384,6 +2384,7 @@ static inline void encode_frame_internal(AV1_COMP *cpi) {
memcpy(cm->lf.ref_deltas, cm->prev_frame->ref_deltas, REF_FRAMES);
memcpy(cm->lf.mode_deltas, cm->prev_frame->mode_deltas, MAX_MODE_LF_DELTAS);
}
+ cm->lf.mode_ref_delta_enabled = oxcf->algo_cfg.mode_ref_delta_enabled;
memcpy(cm->cur_frame->ref_deltas, cm->lf.ref_deltas, REF_FRAMES);
memcpy(cm->cur_frame->mode_deltas, cm->lf.mode_deltas, MAX_MODE_LF_DELTAS);
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index ab858ea9ba..601a111d35 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -886,6 +886,12 @@ typedef struct {
*/
bool skip_postproc_filtering;
+ /*!
+ * Indicates if mode and reference frame delta should be enabled during
+ * loopfiltering.
+ */
+ int mode_ref_delta_enabled;
+
/*!
* Controls screen content detection mode
*/
diff --git a/test/loopfilter_control_test.cc b/test/loopfilter_control_test.cc
index f498559858..7a19a7fbb1 100644
--- a/test/loopfilter_control_test.cc
+++ b/test/loopfilter_control_test.cc
@@ -67,17 +67,18 @@ const TestVideoParam kTestVectors[] = {
{ "niklas_1280_720_30.y4m", 8, AOM_IMG_FMT_I420, AOM_BITS_8, 0 },
};
-// Params: test video, lf_control, aq mode, threads, tile columns.
+// Params: test video, lf_control, aq mode, threads, tile columns,
+// mode_ref_delta_enabled.
class LFControlEndToEndTest
- : public ::libaom_test::CodecTestWith5Params<TestVideoParam, int,
- unsigned int, int, int>,
+ : public ::libaom_test::CodecTestWith6Params<TestVideoParam, int,
+ unsigned int, int, int, int>,
public ::libaom_test::EncoderTest {
protected:
LFControlEndToEndTest()
: EncoderTest(GET_PARAM(0)), test_video_param_(GET_PARAM(1)),
lf_control_(GET_PARAM(2)), psnr_(0.0), nframes_(0),
aq_mode_(GET_PARAM(3)), threads_(GET_PARAM(4)),
- tile_columns_(GET_PARAM(5)) {}
+ tile_columns_(GET_PARAM(5)), mode_ref_delta_enabled_(GET_PARAM(6)) {}
~LFControlEndToEndTest() override = default;
@@ -123,6 +124,8 @@ class LFControlEndToEndTest
encoder->Control(AV1E_SET_MV_COST_UPD_FREQ, 2);
encoder->Control(AV1E_SET_DV_COST_UPD_FREQ, 2);
encoder->Control(AV1E_SET_LOOPFILTER_CONTROL, lf_control_);
+ encoder->Control(AV1E_SET_MODE_REF_DELTA_ENABLED,
+ mode_ref_delta_enabled_);
}
}
@@ -164,14 +167,19 @@ class LFControlEndToEndTest
unsigned int aq_mode_;
int threads_;
int tile_columns_;
+ int mode_ref_delta_enabled_;
};
class LFControlEndToEndTestThreaded : public LFControlEndToEndTest {};
+class LFControlModeRefDeltaEndToEndTestTest : public LFControlEndToEndTest {};
+
TEST_P(LFControlEndToEndTest, EndtoEndPSNRTest) { DoTest(); }
TEST_P(LFControlEndToEndTestThreaded, EndtoEndPSNRTest) { DoTest(); }
+TEST_P(LFControlModeRefDeltaEndToEndTestTest, EndtoEndPSNRTest) { DoTest(); }
+
TEST(LFControlGetterTest, NullptrInput) {
int *lf_level = nullptr;
aom_codec_ctx_t encoder;
@@ -188,11 +196,20 @@ AV1_INSTANTIATE_TEST_SUITE(LFControlEndToEndTest,
::testing::ValuesIn(kTestVectors),
::testing::Range(0, 4),
::testing::Values<unsigned int>(0, 3),
- ::testing::Values(1), ::testing::Values(1));
+ ::testing::Values(1), ::testing::Values(1),
+ ::testing::Values(1));
AV1_INSTANTIATE_TEST_SUITE(LFControlEndToEndTestThreaded,
::testing::ValuesIn(kTestVectors),
::testing::Range(0, 4),
::testing::Values<unsigned int>(0, 3),
- ::testing::Range(2, 5), ::testing::Range(2, 5));
+ ::testing::Range(2, 5), ::testing::Range(2, 5),
+ ::testing::Values(1));
+
+AV1_INSTANTIATE_TEST_SUITE(LFControlModeRefDeltaEndToEndTestTest,
+ ::testing::Values(kTestVectors[0]),
+ ::testing::Range(0, 4),
+ ::testing::Values<unsigned int>(0, 3),
+ ::testing::Values(1), ::testing::Values(1),
+ ::testing::Values(0));
} // namespace