Commit 062220fcbb for aom
commit 062220fcbb1b6a77b63f5307fb2a9af56dc8c5e4
Author: Ihor Buhaiov <freedbrt@gmail.com>
Date: Fri Oct 23 17:56:46 2026 +0200
Allocate for a forced max set before frame 0
With g_forced_max_frame_width/height, one pass encoding with no lag
can upscale within the forced maximum as an inter frame that scales
its references. init_config() sizes the compressor buffers for the
forced maximum. But when the maximum comes from
aom_codec_enc_config_set() after aom_codec_enc_init(),
av1_change_config() only reallocates once the frame size itself grows
past the allocated size.
So the first upscale reallocates the buffers and clears
frame_size_related_setup_done. The next av1_check_initial_width() call
runs init_ref_frame_bufs(), which drops all reference frames.
encoder_set_config() does not force a key frame for this upscale, so
the inter frame fails in av1_encode_strategy() with no references.
Before the first frame, also reallocate when the forced maximum is
larger than the allocated size. The buffers then match the case where
the maximum is set at init. After the first frame the condition is
unchanged, as RaiseForcedMax* expects.
Repro: niklas_640_480_30.yuv, 300 frames. Start at 320x240, set a
forced max of 640x480 with config_set before frame 0, and config_set
to 640x480 before frame 60. After a failed frame, the next frame
forces a key frame. The decoder output is compared with the encoder
recon after every frame. Realtime speeds 7 to 11, 1 and 4 threads,
4:2:0 and 4:4:4, camera and screen tune, realtime only and default
builds. Good quality speeds 5 and 6 in the default build, 90 frames,
upscale at frame 30.
Before: every frame from the upscale on fails (240 of 300 per
realtime run, 60 of 90 per good quality run), forced key frames
included.
After: no failed frames, one key frame per run (the upscale is an
inter frame), no recon mismatches. The bitstreams are the same as
with the forced maximum set at init, except default build at speed 11
with 4 threads, whose output changes from run to run on main too.
The new ForcedMaxSetBeforeFirstFrame tests fail before and pass
after.
Change-Id: Ic27ab1885125a6f7013f6d22677e0f5af51ee234
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 38a555a928..3633b2ffc6 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -1047,8 +1047,18 @@ void av1_change_config(struct AV1_COMP *cpi, const AV1EncoderConfig *oxcf,
cm->superres_upscaled_width = frm_dim_cfg->width;
cm->superres_upscaled_height = frm_dim_cfg->height;
- if (cm->width > cpi->data_alloc_width ||
- cm->height > cpi->data_alloc_height || is_sb_size_changed) {
+ // Before the first frame, size the buffers for the forced maximum like
+ // init_config() does, so a later upscale keeps the reference frames.
+ int alloc_width = cm->width;
+ int alloc_height = cm->height;
+ if (!cpi->ppi->seq_params_locked) {
+ if (frm_dim_cfg->forced_max_frame_width)
+ alloc_width = frm_dim_cfg->forced_max_frame_width;
+ if (frm_dim_cfg->forced_max_frame_height)
+ alloc_height = frm_dim_cfg->forced_max_frame_height;
+ }
+ if (alloc_width > cpi->data_alloc_width ||
+ alloc_height > cpi->data_alloc_height || is_sb_size_changed) {
av1_free_context_buffers(cm);
av1_free_shared_coeff_buffer(&cpi->td.shared_coeff_buf);
av1_free_sms_tree(&cpi->td);
diff --git a/test/forced_max_frame_width_height_test.cc b/test/forced_max_frame_width_height_test.cc
index 3f4b153146..005859d189 100644
--- a/test/forced_max_frame_width_height_test.cc
+++ b/test/forced_max_frame_width_height_test.cc
@@ -27,6 +27,7 @@
#include "aom/aomdx.h"
#endif
#include "gtest/gtest.h"
+#include "test/md5_helper.h"
namespace {
@@ -682,4 +683,100 @@ TEST(EncodeForcedMaxFrameWidthHeight, UpscaleWithoutForcedMaxGoodSSIM) {
}
#endif // !CONFIG_REALTIME_ONLY
+// Sets g_forced_max_frame_width/height with aom_codec_enc_config_set() after
+// aom_codec_enc_init() but before the first frame, then upscales within that
+// maximum. The upscaled frames must be coded as inter frames that reference
+// the smaller frames, as when the forced maximum is set at init time.
+void RunForcedMaxSetBeforeFirstFrameTest(unsigned int usage) {
+ constexpr unsigned int kWidth = 128;
+ constexpr unsigned int kHeight = 128;
+ constexpr unsigned int kMaxWidth = 256;
+ constexpr unsigned int kMaxHeight = 256;
+ constexpr int kFrames = 4;
+ constexpr int kUpscaleFrame = 2;
+ aom_codec_iface_t *iface = aom_codec_av1_cx();
+ aom_codec_enc_cfg_t cfg;
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, usage));
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+ cfg.g_lag_in_frames = 0;
+ cfg.kf_mode = AOM_KF_DISABLED;
+ aom_codec_ctx_t enc;
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
+ ASSERT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AOME_SET_CPUUSED,
+ usage == AOM_USAGE_REALTIME ? 9 : 6));
+ cfg.g_forced_max_frame_width = kMaxWidth;
+ cfg.g_forced_max_frame_height = kMaxHeight;
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg));
+
+#if CONFIG_AV1_DECODER
+ aom_codec_ctx_t dec;
+ ASSERT_EQ(AOM_CODEC_OK,
+ aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0));
+#endif
+
+ aom_image_t *small_image =
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kWidth, kHeight, 1);
+ ASSERT_NE(small_image, nullptr);
+ aom_image_t *large_image =
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kMaxWidth, kMaxHeight, 1);
+ ASSERT_NE(large_image, nullptr);
+ for (int i = 0; i < kFrames; ++i) {
+ if (i == kUpscaleFrame) {
+ cfg.g_w = kMaxWidth;
+ cfg.g_h = kMaxHeight;
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg));
+ }
+ aom_image_t *image = i < kUpscaleFrame ? small_image : large_image;
+ FillImagePattern(image, i);
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, image, i, 1, 0))
+ << "frame " << i << ": " << aom_codec_error_detail(&enc);
+ aom_codec_iter_t iter = nullptr;
+ const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ EXPECT_EQ((pkt->data.frame.flags & AOM_FRAME_IS_KEY) != 0, i == 0)
+ << "frame " << i;
+
+#if CONFIG_AV1_DECODER
+ ASSERT_EQ(AOM_CODEC_OK,
+ aom_codec_decode(
+ &dec, static_cast<const uint8_t *>(pkt->data.frame.buf),
+ pkt->data.frame.sz, nullptr));
+ aom_codec_iter_t dec_iter = nullptr;
+ const aom_image_t *decoded = aom_codec_get_frame(&dec, &dec_iter);
+ ASSERT_NE(decoded, nullptr);
+ EXPECT_EQ(decoded->d_w, image->d_w);
+ EXPECT_EQ(decoded->d_h, image->d_h);
+ aom_image_t recon;
+ ASSERT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AV1_GET_NEW_FRAME_IMAGE, &recon));
+ ::libaom_test::MD5 recon_md5;
+ recon_md5.Add(&recon);
+ ::libaom_test::MD5 decoded_md5;
+ decoded_md5.Add(decoded);
+ EXPECT_STREQ(recon_md5.Get(), decoded_md5.Get()) << "frame " << i;
+#endif
+ }
+
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0));
+ aom_img_free(small_image);
+ aom_img_free(large_image);
+#if CONFIG_AV1_DECODER
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_destroy(&dec));
+#endif
+ ASSERT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
+}
+
+TEST(EncodeForcedMaxFrameWidthHeight, ForcedMaxSetBeforeFirstFrameRealtime) {
+ RunForcedMaxSetBeforeFirstFrameTest(AOM_USAGE_REALTIME);
+}
+
+#if !CONFIG_REALTIME_ONLY
+TEST(EncodeForcedMaxFrameWidthHeight, ForcedMaxSetBeforeFirstFrameGoodQuality) {
+ RunForcedMaxSetBeforeFirstFrameTest(AOM_USAGE_GOOD_QUALITY);
+}
+#endif // !CONFIG_REALTIME_ONLY
+
} // namespace