Commit 33b6f7d5e8 for aom
commit 33b6f7d5e8be35e4a176cd7a19447b469c500d28
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Mon Oct 12 09:15:42 2026 +0200
Match the decoder's prev segment map on resize
The encoder took cm->last_frame_seg_map from the previous frame
whatever its size. After a frame size change it read that map with the
new mi_cols, past its end when the previous frame was smaller, and
builds with asserts could stop on segment_id < MAX_SEGMENTS in
get_segment_id(). When the map isn't updated on that frame, the
encoder also took the segment ids from it, while the decoder uses
segment 0 for every block, so the stream fails to decode.
Use the previous map only when its size matches, like the decoder.
When the map isn't updated and there is no previous map, clear the
frame's map as the decoder does, so the next frame predicts from the
same map.
The new test sets an active map once and codes one frame at half
size. Without this change every case fails to decode.
Change-Id: Ia41c857cfbc1f94bff97fa47a0c4469c06aa2095
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 3a53e6830f..f594e83056 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -2474,8 +2474,12 @@ static inline void encode_frame_internal(AV1_COMP *cpi) {
init_encode_frame_mb_context(cpi);
set_default_interp_skip_flags(cm, &cpi->interp_search_flags);
+ // Like the decoder, only use the previous segment map if it has the size of
+ // this frame.
if (cm->prev_frame && cm->prev_frame->seg.enabled &&
- cpi->svc.number_spatial_layers == 1)
+ cpi->svc.number_spatial_layers == 1 &&
+ cm->prev_frame->mi_rows == cm->mi_params.mi_rows &&
+ cm->prev_frame->mi_cols == cm->mi_params.mi_cols)
cm->last_frame_seg_map = cm->prev_frame->seg_map;
else
cm->last_frame_seg_map = NULL;
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 2702a2abc6..dea9aa6d65 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -4498,11 +4498,15 @@ static int encode_frame_to_data_rate(AV1_COMP *cpi, size_t *size, uint8_t *dest,
av1_dump_filtered_recon_frames(cpi);
#endif // DUMP_RECON_FRAMES
- if (cm->seg.enabled) {
- if (cm->seg.update_map == 0 && cm->last_frame_seg_map) {
- memcpy(cm->cur_frame->seg_map, cm->last_frame_seg_map,
- cm->cur_frame->mi_cols * cm->cur_frame->mi_rows *
- sizeof(*cm->cur_frame->seg_map));
+ if (cm->seg.enabled && cm->seg.update_map == 0) {
+ const size_t seg_map_size = cm->cur_frame->mi_cols *
+ cm->cur_frame->mi_rows *
+ sizeof(*cm->cur_frame->seg_map);
+ // Without a previous map the decoder uses segment 0 everywhere.
+ if (cm->last_frame_seg_map) {
+ memcpy(cm->cur_frame->seg_map, cm->last_frame_seg_map, seg_map_size);
+ } else {
+ memset(cm->cur_frame->seg_map, 0, seg_map_size);
}
}
diff --git a/test/active_map_test.cc b/test/active_map_test.cc
index b3beb7a277..5206940511 100644
--- a/test/active_map_test.cc
+++ b/test/active_map_test.cc
@@ -336,6 +336,7 @@ constexpr int kWinSize = 128;
constexpr int kDocHeight = 1024;
constexpr int kNumFrames = 60;
constexpr int kEdgeMargin = 8;
+constexpr int kResizeFrame = 20;
void WindowPos(int frame, int *x, int *y) {
static const int kPos[4][2] = {
@@ -485,6 +486,10 @@ class InactiveAreaTest
encoder->Control(AV1E_SET_ROW_MT, cfg_.g_threads > 1);
return;
}
+ if (resize_ && frame == kResizeFrame) {
+ aom_scaling_mode_t mode = { AOME_ONETWO, AOME_ONETWO };
+ encoder->Control(AOME_SET_SCALEMODE, &mode);
+ }
if (map_once_ && frame > 1) return;
uint8_t map[kMapRows * kMapCols];
@@ -518,6 +523,8 @@ class InactiveAreaTest
void DecompressedFrameHook(const aom_image_t &img,
aom_codec_pts_t pts) override {
+ // Only check that the encoder and decoder match.
+ if (resize_) return;
const int frame = static_cast<int>(pts);
if (frame > 0 && !skip_ref_golden_) {
uint8_t map[kMapRows * kMapCols];
@@ -573,11 +580,12 @@ class InactiveAreaTest
}
}
- void DoTest(bool use_roi, bool skip_ref_golden = false,
- bool map_once = false) {
+ void DoTest(bool use_roi, bool skip_ref_golden = false, bool map_once = false,
+ bool resize = false) {
use_roi_ = use_roi;
skip_ref_golden_ = skip_ref_golden;
map_once_ = map_once;
+ resize_ = resize;
// More bits let q drop low enough while the map stays set.
if (map_once) cfg_.rc_target_bitrate = 1000;
ScrollingWindowSource video(map_once);
@@ -590,6 +598,7 @@ class InactiveAreaTest
bool use_roi_ = false;
bool skip_ref_golden_ = false;
bool map_once_ = false;
+ bool resize_ = false;
std::vector<uint8_t> prev_[3];
};
@@ -602,6 +611,12 @@ TEST_P(InactiveAreaTest, RoiSkipGoldenRef) { DoTest(true, true); }
// The map stays set while cyclic refresh turns off and q drops.
TEST_P(InactiveAreaTest, ActiveMapSetOnce) { DoTest(false, false, true); }
+// One frame has another size, so the frames around it can't use the segment
+// map of the frame before them.
+TEST_P(InactiveAreaTest, ActiveMapSetOnceResize) {
+ DoTest(false, false, true, true);
+}
+
AV1_INSTANTIATE_TEST_SUITE(
InactiveAreaTest, ::testing::Values(::libaom_test::kRealTime),
::testing::Range(7, 12), ::testing::Values(0, 3), ::testing::Values(1, 4),