Fix y_stride_src in init_motion_estimation() Fix y_stride_src in init_motion_estimation() to derive it from cpi->unscaled_source when available instead of lookahead->buf[0], which can hold a stale stride from an earlier slot. Also check fpf_y_stride when determining whether search_site_cfg should be updated. Unittest added. Bug: 568044446 Change-Id: I4601459b461d1fc3af354863c4760a31a5277c62 (cherry picked from commit 12e2b14964e3390dabd08f8d70a752cc905409b2)
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c index 4485b49..9946836 100644 --- a/av1/encoder/encoder.c +++ b/av1/encoder/encoder.c
@@ -2485,11 +2485,18 @@ const int aligned_width = (cm->width + 7) & ~7; const int y_stride = aom_calc_y_stride(aligned_width, cpi->oxcf.border_in_pixels); + // cpi->ppi->lookahead->buf is a multi-slot buffer, so buf[0] may not be + // the current frame's slot and can retain an old stride after a frame resize + // in av1_lookahead_push(). During av1_encode(), cpi->unscaled_source points + // to the current frame's lookahead buffer entry (frame_input->source). + const int lookahead_y_stride = cpi->unscaled_source != NULL + ? cpi->unscaled_source->y_stride + : cpi->ppi->lookahead->buf[0].img.y_stride; const int y_stride_src = ((cpi->oxcf.frm_dim_cfg.width != cm->width || cpi->oxcf.frm_dim_cfg.height != cm->height) || av1_superres_scaled(cm)) ? y_stride - : cpi->ppi->lookahead->buf->img.y_stride; + : lookahead_y_stride; int fpf_y_stride = cm->cur_frame != NULL ? cm->cur_frame->buf.y_stride : y_stride; @@ -2498,10 +2505,13 @@ const int should_update = !mv_search_params->search_site_cfg[SS_CFG_SRC][DIAMOND].stride || !mv_search_params->search_site_cfg[SS_CFG_LOOKAHEAD][DIAMOND].stride || + !mv_search_params->search_site_cfg[SS_CFG_FPF][DIAMOND].stride || (y_stride != mv_search_params->search_site_cfg[SS_CFG_SRC][DIAMOND].stride) || (y_stride_src != - mv_search_params->search_site_cfg[SS_CFG_LOOKAHEAD][DIAMOND].stride); + mv_search_params->search_site_cfg[SS_CFG_LOOKAHEAD][DIAMOND].stride) || + (fpf_y_stride != + mv_search_params->search_site_cfg[SS_CFG_FPF][DIAMOND].stride); if (!should_update) { return;
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc index 4c4445e..473b18d 100644 --- a/test/encode_api_test.cc +++ b/test/encode_api_test.cc
@@ -1534,8 +1534,14 @@ EncodeOne(&enc, img_small, 0); - // Only the height grows, so the reconstructed frame keeps its stride while - // the source buffer gets a new one. + // Only the height grows, so the reconstructed frame keeps its stride + // (SS_CFG_SRC) while the current lookahead source buffer is reallocated in + // av1_lookahead_push() with AOM_BORDER_IN_PIXELS (160) instead of + // oxcf->border_in_pixels (96), changing its stride (SS_CFG_LOOKAHEAD). + // Furthermore, for GOOD_QUALITY and REALTIME modes (even with + // lag_in_frames = 0), the lookahead ring buffer has 2 slots (max_pre_frames = + // 1): frame 0 was placed in buf[0], so frame 1 is placed in buf[1] while + // buf[0] retains the old stride. cfg.g_h = 64; ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, AOM_EFLAG_FORCE_KF), @@ -1615,6 +1621,78 @@ } #if !CONFIG_REALTIME_ONLY +// Tests that search_site_cfg[SS_CFG_LOOKAHEAD] in init_motion_estimation() is +// updated using the current frame's unscaled_source->y_stride rather than +// lookahead->buf[0].img.y_stride. In TPL's motion_estimation() (tpl_model.c), +// when the reference buffer is an unscaled lookahead source buffer whose stride +// differs from the reconstructed frame stride (SS_CFG_SRC), it directly selects +// search_site_cfg[SS_CFG_LOOKAHEAD] and asserts that search_site_cfg->stride == +// ref_stride (without calling av1_get_search_site_config()). +TEST(EncodeAPI, TplSubsamplingChangeGoodQuality) { + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY), + AOM_CODEC_OK); + cfg.g_w = 64; + cfg.g_h = 64; + cfg.g_lag_in_frames = 4; + cfg.g_threads = 1; + cfg.monochrome = 1; + + aom_codec_ctx_t enc; + ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 5), AOM_CODEC_OK); + // Disable temporal filtering so init_gop_frames_for_tpl() uses the lookahead + // buffers (&buf->img, y_stride = 384) directly as tpl_frame->gf_picture + // rather than replacing them with tf_buf (y_stride = 256 == SS_CFG_SRC). + ASSERT_EQ(aom_codec_control(&enc, AOME_SET_ARNR_MAXFRAMES, 0), AOM_CODEC_OK); + + aom_image_t *img_420 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1); + ASSERT_NE(img_420, nullptr); + FillImageRandom(img_420); + aom_image_t *img_444 = aom_img_alloc(nullptr, AOM_IMG_FMT_I444, 64, 64, 1); + ASSERT_NE(img_444, nullptr); + FillImageRandom(img_444); + + // Frame 0 (I420) goes into lookahead->buf[0] with border_in_pixels = 96 + // (y_stride = 256), and is then flushed. + ASSERT_EQ(aom_codec_encode(&enc, img_420, 0, 1, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK); + { + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_cx_data(&enc, &iter) != nullptr) { + } + } + + // Frames 1..4 (I444) go into lookahead->buf[1..4]. Because monochrome = 1, + // changing subsampling from I420 to I444 is valid across frames and triggers + // new_dimensions in av1_lookahead_push(), reallocating buf[1..4] with + // AOM_BORDER_IN_PIXELS = 160 (y_stride = 384) while buf[0] keeps + // y_stride = 256. + // Because av1_tpl_setup_stats() runs before av1_encode() updates + // cpi->unscaled_source, frame 1 is encoded first (as a 1-frame GOP) to update + // cpi->unscaled_source to buf[1] (y_stride = 384). Forcing a key frame on + // frame 2 starts a new GOP at frame 2 so init_motion_estimation() and TPL run + // on frames 2..4 (in slots buf[2..4]). + ASSERT_EQ(aom_codec_encode(&enc, img_444, 1, 1, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_encode(&enc, img_444, 2, 1, AOM_EFLAG_FORCE_KF), + AOM_CODEC_OK); + ASSERT_EQ(aom_codec_encode(&enc, img_444, 3, 1, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_encode(&enc, img_444, 4, 1, 0), AOM_CODEC_OK); + // Each flush (nullptr) call drains 1 frame from the lookahead buffer, so 4 + // calls are needed for frames 1..4. + for (int i = 0; i < 4; ++i) { + ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK); + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_cx_data(&enc, &iter) != nullptr) { + } + } + + aom_img_free(img_420); + aom_img_free(img_444); + ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK); +} + TEST(EncodeAPI, AllIntraMode) { aom_codec_iface_t *iface = aom_codec_av1_cx(); aom_codec_ctx_t enc;