Commit ef2ac20fe9 for aom
commit ef2ac20fe987f8a0b7aa675f7644239a9b6cb6e7
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Wed Oct 7 11:00:49 2026 +0200
rtc: Restore num_proj_ref after a rejected merge
direct_partition_merging() calls find_predictors() on the top left
block to test the merge, and find_predictors() sets num_proj_ref to 1.
When the merge is rejected, the block keeps that 1 instead of the
sample count from pickmode.
The bitstream writer reads num_proj_ref in motion_mode_allowed(). If
the real count is 0, the encoder writes a motion_mode symbol, but the
decoder computes the count itself and reads an obmc symbol. Decoding
fails from that frame on.
It only happens with warped motion allowed, camera content, speed 8
or higher and a frame without CDF updates.
Restore num_proj_ref after a rejected merge. Add RTMotionModeTest. It
fails without the fix with a decode error on frame 83.
Change-Id: I12b17e7e0e23e597fe2324a1045b7dcfa5d7fda9
diff --git a/av1/encoder/partition_search.c b/av1/encoder/partition_search.c
index c038838bc0..55ff863b74 100644
--- a/av1/encoder/partition_search.c
+++ b/av1/encoder/partition_search.c
@@ -2819,6 +2819,7 @@ static void direct_partition_merging(AV1_COMP *cpi, ThreadData *td,
MB_MODE_INFO **this_mi = mib;
BLOCK_SIZE orig_bsize = this_mi[0]->bsize;
const PARTITION_TYPE orig_partition = this_mi[0]->partition;
+ const uint8_t orig_num_proj_ref = this_mi[0]->num_proj_ref;
this_mi[0]->bsize = bsize;
this_mi[0]->partition = PARTITION_NONE;
@@ -2864,6 +2865,7 @@ static void direct_partition_merging(AV1_COMP *cpi, ThreadData *td,
mi_col, this_mi[0]->bsize);
find_predictors(cpi, x, ref_frame, frame_mv, yv12_mb, this_mi[0]->bsize,
force_skip_low_temp_var, skip_pred_mv, &use_scaled_ref);
+ this_mi[0]->num_proj_ref = orig_num_proj_ref;
} else {
struct scale_factors *sf = get_ref_scale_factors(cm, ref_frame);
const int is_scaled = av1_is_scaled(sf);
diff --git a/test/rt_end_to_end_test.cc b/test/rt_end_to_end_test.cc
index bbc94e3cd9..e1ff3a1972 100644
--- a/test/rt_end_to_end_test.cc
+++ b/test/rt_end_to_end_test.cc
@@ -16,8 +16,11 @@
#include "gtest/gtest.h"
+#include "config/aom_config.h"
+
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
+#include "test/i420_video_source.h"
#include "test/util.h"
#include "test/y4m_video_source.h"
#include "test/yuv_video_source.h"
@@ -205,4 +208,44 @@ AV1_INSTANTIATE_TEST_SUITE(RTEndToEndTestThreaded,
::testing::Values<unsigned int>(0, 3),
::testing::Range(2, 6), ::testing::Range(1, 5),
::testing::Range(1, 5));
+
+#if !CONFIG_REALTIME_ONLY
+// Keeps warped motion and OBMC at their defaults, so motion mode symbols are
+// coded.
+class RTMotionModeTest : public ::libaom_test::CodecTestWithParam<int>,
+ public ::libaom_test::EncoderTest {
+ protected:
+ RTMotionModeTest() : EncoderTest(GET_PARAM(0)), cpu_used_(GET_PARAM(1)) {}
+ ~RTMotionModeTest() override = default;
+
+ void SetUp() override {
+ InitializeConfig(::libaom_test::kRealTime);
+ cfg_.g_threads = 1;
+ cfg_.rc_end_usage = AOM_CBR;
+ cfg_.rc_target_bitrate = 150;
+ }
+
+ void PreEncodeFrameHook(::libaom_test::VideoSource *video,
+ ::libaom_test::Encoder *encoder) override {
+ if (video->frame() == 0) {
+ encoder->Control(AOME_SET_CPUUSED, cpu_used_);
+ encoder->Control(AV1E_SET_TUNE_CONTENT, AOM_CONTENT_DEFAULT);
+ // Partition merging only runs on frames without CDF updates.
+ encoder->Control(AV1E_SET_CDF_UPDATE_MODE, 0);
+ }
+ }
+
+ int cpu_used_;
+};
+
+// A rejected partition merge must not change the motion mode inputs of the
+// block it tried to merge.
+TEST_P(RTMotionModeTest, RejectedMergeKeepsEncoderDecoderMatch) {
+ ::libaom_test::I420VideoSource video("niklas_640_480_30.yuv", 640, 480, 30, 1,
+ 0, 100);
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
+AV1_INSTANTIATE_TEST_SUITE(RTMotionModeTest, ::testing::Values(8));
+#endif // !CONFIG_REALTIME_ONLY
} // namespace