Add codec control AOMD_SET_FRAME_SIZE_LIMIT Add the codec control AOMD_SET_FRAME_SIZE_LIMIT for the decoder. If nonzero, it specifies the maximum frame size (width * height). 0 (the default) means unlimited. Bug: 485932002 Change-Id: I7823303f9c2ec3c37988e84a04022929dee1ff40
diff --git a/aom/aomdx.h b/aom/aomdx.h index f18cbbf..4c32beb 100644 --- a/aom/aomdx.h +++ b/aom/aomdx.h
@@ -466,6 +466,15 @@ * be used. */ AV1D_GET_MI_INFO, + + /*!\brief Codec control function to set the maximum frame size, unsigned int + * parameter + * + * - 0 = unlimited (default) + * - nonzero = frame size (width * height) must be less than or equal to this + * maximum + */ + AOMD_SET_FRAME_SIZE_LIMIT, }; /*!\cond */ @@ -598,6 +607,9 @@ // The AOM_CTRL_USE_TYPE macro can't be used with AV1D_GET_MI_INFO because // AV1D_GET_MI_INFO takes more than one parameter. #define AOM_CTRL_AV1D_GET_MI_INFO + +AOM_CTRL_USE_TYPE(AOMD_SET_FRAME_SIZE_LIMIT, unsigned int) +#define AOM_CTRL_AOMD_SET_FRAME_SIZE_LIMIT /*!\endcond */ /*! @} - end defgroup aom_decoder */ #ifdef __cplusplus
diff --git a/av1/av1_dx_iface.c b/av1/av1_dx_iface.c index be4b1a7..f880df8 100644 --- a/av1/av1_dx_iface.c +++ b/av1/av1_dx_iface.c
@@ -62,6 +62,7 @@ unsigned int is_annexb; int operating_point; int output_all_layers; + unsigned int frame_size_limit; AVxWorker *frame_worker; @@ -112,6 +113,8 @@ priv->tile_mode = 0; priv->decode_tile_row = -1; priv->decode_tile_col = -1; + + priv->frame_size_limit = 0; } return AOM_CODEC_OK; @@ -485,6 +488,8 @@ // thread or loopfilter thread. frame_worker_data->pbi->max_threads = ctx->cfg.threads; frame_worker_data->pbi->inv_tile_order = ctx->invert_tile_order; + frame_worker_data->pbi->common.decode_frame_size_limit = + ctx->frame_size_limit; frame_worker_data->pbi->common.tiles.large_scale = ctx->tile_mode; frame_worker_data->pbi->is_annexb = ctx->is_annexb; frame_worker_data->pbi->dec_tile_row = ctx->decode_tile_row; @@ -1584,6 +1589,12 @@ return AOM_CODEC_OK; } +static aom_codec_err_t ctrl_set_frame_size_limit(aom_codec_alg_priv_t *ctx, + va_list args) { + ctx->frame_size_limit = va_arg(args, unsigned int); + return AOM_CODEC_OK; +} + static aom_codec_err_t ctrl_get_accounting(aom_codec_alg_priv_t *ctx, va_list args) { #if !CONFIG_ACCOUNTING @@ -1691,6 +1702,7 @@ { AV1D_SET_ROW_MT, ctrl_set_row_mt }, { AV1D_SET_EXT_REF_PTR, ctrl_set_ext_ref_ptr }, { AV1D_SET_SKIP_FILM_GRAIN, ctrl_set_skip_film_grain }, + { AOMD_SET_FRAME_SIZE_LIMIT, ctrl_set_frame_size_limit }, // Getters { AOMD_GET_FRAME_CORRUPTED, ctrl_get_frame_corrupted },
diff --git a/av1/common/av1_common_int.h b/av1/common/av1_common_int.h index 715fe2c..b3edab0 100644 --- a/av1/common/av1_common_int.h +++ b/av1/common/av1_common_int.h
@@ -822,6 +822,13 @@ uint8_t superres_scale_denominator; /*! + * If nonzero, the maximum frame size (width * height). 0 means unlimited. + * Note: Only used by the decoder. Defined in the AV1_COMMON struct for + * convenience. + */ + unsigned int decode_frame_size_limit; + + /*! * buffer_removal_times[op_num] specifies the frame removal time in units of * DecCT clock ticks counted from the removal time of the last random access * point for operating point op_num.
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c index 69e1e13..32974c2 100644 --- a/av1/decoder/decodeframe.c +++ b/av1/decoder/decodeframe.c
@@ -1957,6 +1957,12 @@ "Dimensions of %dx%d beyond allowed size of %dx%d.", width, height, DECODE_WIDTH_LIMIT, DECODE_HEIGHT_LIMIT); #endif + if (cm->decode_frame_size_limit && + (uint64_t)width * height > cm->decode_frame_size_limit) { + aom_internal_error(cm->error, AOM_CODEC_CORRUPT_FRAME, + "Dimensions of %dx%d beyond allowed size of %u.", width, + height, cm->decode_frame_size_limit); + } if (cm->width != width || cm->height != height) { const int new_mi_rows = CEIL_POWER_OF_TWO(height, MI_SIZE_LOG2); const int new_mi_cols = CEIL_POWER_OF_TWO(width, MI_SIZE_LOG2);
diff --git a/test/decode_frame_size_limit_test.cc b/test/decode_frame_size_limit_test.cc new file mode 100644 index 0000000..11d4921 --- /dev/null +++ b/test/decode_frame_size_limit_test.cc
@@ -0,0 +1,107 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved. + * + * This source code is subject to the terms of the BSD 2 Clause License and + * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License + * was not distributed with this source code in the LICENSE file, you can + * obtain it at www.aomedia.org/license/software. If the Alliance for Open + * Media Patent License 1.0 was not distributed with this source code in the + * PATENTS file, you can obtain it at www.aomedia.org/license/patent. + */ + +#include <ostream> + +#include "gtest/gtest.h" +#include "test/codec_factory.h" +#include "test/decode_test_driver.h" +#include "test/ivf_video_source.h" +#include "test/util.h" +#include "test/video_source.h" + +namespace { + +struct DecodeParam { + const char *filename; + unsigned int width; + unsigned int height; +}; + +std::ostream &operator<<(std::ostream &os, const DecodeParam &dp) { + return os << "file: " << dp.filename; +} + +class DecodeFrameSizeLimitTest + : public ::libaom_test::DecoderTest, + public ::libaom_test::CodecTestWithParam<DecodeParam> { + protected: + DecodeFrameSizeLimitTest() + : DecoderTest(GET_PARAM(0)), width_(GET_PARAM(1).width), + height_(GET_PARAM(1).height) {} + + ~DecodeFrameSizeLimitTest() override = default; + + void PreDecodeFrameHook(const libaom_test::CompressedVideoSource &video, + libaom_test::Decoder *decoder) override { + if (video.frame_number() == 0) + decoder->Control(AOMD_SET_FRAME_SIZE_LIMIT, frame_size_limit_); + } + + void DecompressedFrameHook(const aom_image_t &img, + const unsigned int /*frame_number*/) override { + EXPECT_EQ(img.d_w, width_); + EXPECT_EQ(img.d_h, height_); + } + + bool HandleDecodeResult(const aom_codec_err_t res_dec, + const libaom_test::CompressedVideoSource & /*video*/, + libaom_test::Decoder * /*decoder*/) override { + bool expect_failure = + frame_size_limit_ && width_ * height_ > frame_size_limit_; + if (expect_failure) { + EXPECT_EQ(res_dec, AOM_CODEC_CORRUPT_FRAME); + } else { + EXPECT_EQ(res_dec, AOM_CODEC_OK); + } + + return !HasFailure(); + } + + void RunTest() { + const DecodeParam input = GET_PARAM(1); + aom_codec_dec_cfg_t cfg = { 1, 0, 0, !FORCE_HIGHBITDEPTH_DECODING }; + libaom_test::IVFVideoSource decode_video(input.filename); + decode_video.Init(); + + ASSERT_NO_FATAL_FAILURE(RunLoop(&decode_video, cfg)); + } + + protected: + unsigned int frame_size_limit_ = 0; + + private: + unsigned int width_; + unsigned int height_; +}; + +TEST_P(DecodeFrameSizeLimitTest, Unlimited) { RunTest(); } + +TEST_P(DecodeFrameSizeLimitTest, LimitedBig) { + frame_size_limit_ = 226 * 210; + RunTest(); +} + +TEST_P(DecodeFrameSizeLimitTest, LimitedSmall) { + frame_size_limit_ = 226 * 210 - 1; + RunTest(); +} + +const DecodeParam kAV1DecodeFrameSizeLimitTests[] = { + // { filename, width, height } + { "av1-1-b8-01-size-16x16.ivf", 16, 16 }, + { "av1-1-b8-01-size-226x210.ivf", 226, 210 }, +}; + +AV1_INSTANTIATE_TEST_SUITE(DecodeFrameSizeLimitTest, + ::testing::ValuesIn(kAV1DecodeFrameSizeLimitTests)); + +} // namespace
diff --git a/test/test.cmake b/test/test.cmake index 9e5e915..8877b55 100644 --- a/test/test.cmake +++ b/test/test.cmake
@@ -51,6 +51,7 @@ add_to_libaom_test_srcs(AOM_UNIT_TEST_COMMON_SOURCES) list(APPEND AOM_UNIT_TEST_DECODER_SOURCES "${AOM_ROOT}/test/decode_api_test.cc" + "${AOM_ROOT}/test/decode_frame_size_limit_test.cc" "${AOM_ROOT}/test/decode_scalability_test.cc" "${AOM_ROOT}/test/external_frame_buffer_test.cc" "${AOM_ROOT}/test/invalid_file_test.cc"