Commit de40456be1 for aom
commit de40456be1127e94bd94b4d5fd15f1947577a1f9
Author: James Zern <jzern@google.com>
Date: Thu Apr 16 22:26:49 2026 -0700
encoder: Add check to validate source input
Return AOM_CODEC_INVALID_PARAM if the source input is outside the valid
pixel range for 8/10/12-bit..
The check is wrapped around a new control:
AOME_SET_VALIDATE_INPUT_HBD,
and is enabled by default.
Check is done only with CONFIG_AV1_HIGHBITDEPTH build.
This is a port from libvpx:
090dd8b8 vp9: Add check to validate source input
Bug: 503171644, 503187935, 502933723
Change-Id: I95c76c5c9b5fb1adc80357ffffae19b08c556c3a
diff --git a/aom/aomcx.h b/aom/aomcx.h
index 70c2dad23b..8d14b41034 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1631,6 +1631,14 @@ enum aome_enc_control_id {
*/
AV1E_GET_GOP_INFO,
+ /*!\brief Codec control function to validate HBD input.
+ *
+ * AV1 allows the encoder to validate the high bitdepth (HBD) input and
+ * ensure that every pixel is within the valid range. To disable/enable,
+ * set this parameter to 0/1. The default value is set to be 1.
+ */
+ AOME_SET_VALIDATE_INPUT_HBD,
+
// Any new encoder control IDs should be added above.
// Maximum allowed encoder control ID is 229.
// No encoder control ID should be added below.
@@ -2335,6 +2343,9 @@ AOM_CTRL_USE_TYPE(AV1E_SET_LOOPFILTER_CONTROL, int)
AOM_CTRL_USE_TYPE(AOME_GET_LOOPFILTER_LEVEL, int *)
#define AOM_CTRL_AOME_GET_LOOPFILTER_LEVEL
+AOM_CTRL_USE_TYPE(AOME_SET_VALIDATE_INPUT_HBD, int)
+#define AOM_CTRL_AOME_SET_VALIDATE_INPUT_HBD
+
AOM_CTRL_USE_TYPE(AV1E_SET_AUTO_INTRA_TOOLS_OFF, unsigned int)
#define AOM_CTRL_AV1E_SET_AUTO_INTRA_TOOLS_OFF
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index e726da605d..120f6f7e8b 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -246,6 +246,7 @@ struct av1_extracfg {
int kf_max_pyr_height;
int sb_qp_sweep;
aom_screen_detection_mode screen_detection_mode;
+ unsigned int validate_input_hbd;
};
#if !CONFIG_REALTIME_ONLY
@@ -402,6 +403,7 @@ static const struct av1_extracfg default_extra_cfg = {
-1, // kf_max_pyr_height
0, // sb_qp_sweep
AOM_SCREEN_DETECTION_STANDARD,
+ 1, // validate_input_hbd
};
#else
// Some settings are changed for realtime only build.
@@ -558,6 +560,7 @@ static const struct av1_extracfg default_extra_cfg = {
-1, // kf_max_pyr_height
0, // sb_qp_sweep
AOM_SCREEN_DETECTION_STANDARD,
+ 1, // validate_input_hbd
};
#endif
@@ -1017,6 +1020,31 @@ static aom_codec_err_t validate_img(aom_codec_alg_priv_t *ctx,
}
#endif
+#if CONFIG_AV1_HIGHBITDEPTH
+ if (ctx->extra_cfg.validate_input_hbd &&
+ (img->fmt & AOM_IMG_FMT_HIGHBITDEPTH)) {
+ const unsigned int bit_depth = ctx->oxcf.input_cfg.input_bit_depth;
+ const int max_val = 1 << bit_depth;
+ // Note there is no high bitdepth version of NV12 defined. If one is
+ // added, `num_planes` should be 2 in that case.
+ const int num_planes = img->monochrome ? 1 : 3;
+ for (int plane = 0; plane < num_planes; ++plane) {
+ const unsigned short *src = (const unsigned short *)img->planes[plane];
+ const unsigned int stride = img->stride[plane] / 2;
+ const unsigned int ph = aom_img_plane_height(img, plane);
+ const unsigned int pw = aom_img_plane_width(img, plane);
+ for (unsigned int i = 0; i < ph; ++i) {
+ for (unsigned int j = 0; j < pw; ++j) {
+ if (src[j] >= max_val) {
+ return AOM_CODEC_INVALID_PARAM;
+ }
+ }
+ src += stride;
+ }
+ }
+ }
+#endif // CONFIG_AV1_HIGHBITDEPTH
+
return AOM_CODEC_OK;
}
@@ -1884,6 +1912,13 @@ static aom_codec_err_t ctrl_set_enable_keyframe_filtering(
return update_extra_cfg(ctx, &extra_cfg);
}
+static aom_codec_err_t ctrl_set_validate_input_hbd(aom_codec_alg_priv_t *ctx,
+ va_list args) {
+ struct av1_extracfg extra_cfg = ctx->extra_cfg;
+ extra_cfg.validate_input_hbd = CAST(AOME_SET_VALIDATE_INPUT_HBD, args);
+ return update_extra_cfg(ctx, &extra_cfg);
+}
+
static aom_codec_err_t ctrl_set_arnr_max_frames(aom_codec_alg_priv_t *ctx,
va_list args) {
struct av1_extracfg extra_cfg = ctx->extra_cfg;
@@ -5021,6 +5056,7 @@ static aom_codec_ctrl_fn_map_t encoder_ctrl_maps[] = {
ctrl_set_screen_content_detection_mode },
{ AV1E_SET_ENABLE_ADAPTIVE_SHARPNESS, ctrl_set_enable_adaptive_sharpness },
{ AV1E_SET_EXTERNAL_RATE_CONTROL, ctrl_set_external_rate_control },
+ { AOME_SET_VALIDATE_INPUT_HBD, ctrl_set_validate_input_hbd },
// Getters
{ AOME_GET_LAST_QUANTIZER, ctrl_get_quantizer },
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index 34d92fc039..7f43a72340 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -16,6 +16,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
+#include <initializer_list>
#include <memory>
#include <tuple>
@@ -26,6 +27,7 @@
#include "aom/aomcx.h"
#include "aom/aom_encoder.h"
#include "aom/aom_image.h"
+#include "aom_mem/aom_mem.h"
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
@@ -322,6 +324,83 @@ TEST(EncodeAPI, InvalidUVStrides) {
}
}
+#if CONFIG_AV1_HIGHBITDEPTH
+TEST(EncodeAPI, InvalidInputRange) {
+ constexpr int kWidth = 64;
+ constexpr int kHeight = 64;
+ aom_codec_enc_cfg_t cfg;
+
+ ASSERT_EQ(aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg,
+ /*usage=*/AOM_USAGE_REALTIME),
+ AOM_CODEC_OK);
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+
+ std::unique_ptr<aom_image_t, decltype(&aom_img_free)> image(
+ aom_img_alloc(nullptr, AOM_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0),
+ &aom_img_free);
+ aom_image_t *img = image.get();
+ ASSERT_NE(img, nullptr);
+
+ for (const auto bitdepth : { AOM_BITS_8, AOM_BITS_10, AOM_BITS_12 }) {
+ cfg.g_profile = (bitdepth == AOM_BITS_12) ? 2 : 0;
+ cfg.g_bit_depth = bitdepth;
+
+ // A value that's slightly over the max is used to ensure when the range is
+ // not checked an encode won't produce any integer overflows. Values
+ // outside of the valid range are not supported, so overflows will be
+ // ignored in that case.
+ const int val = 1 << bitdepth;
+ for (const int plane : { AOM_PLANE_Y, AOM_PLANE_U, AOM_PLANE_V }) {
+ const int width = aom_img_plane_width(img, plane);
+ const int height = aom_img_plane_height(img, plane);
+ uint8_t *p = img->planes[plane];
+ for (int y = 0; y < height; ++y) {
+ aom_memset16(p, val, width);
+ p += img->stride[plane];
+ }
+ }
+
+ for (cfg.monochrome = 0; cfg.monochrome <= 1; ++cfg.monochrome) {
+ aom_codec_ctx_t enc;
+ ASSERT_EQ(aom_codec_enc_init(&enc, aom_codec_av1_cx(), &cfg,
+ AOM_CODEC_USE_HIGHBITDEPTH),
+ AOM_CODEC_OK);
+
+ for (int check_input_range = 0; check_input_range <= 1;
+ ++check_input_range) {
+ uint8_t *u = img->planes[AOM_PLANE_U];
+ uint8_t *v = img->planes[AOM_PLANE_V];
+ img->monochrome = cfg.monochrome;
+ if (img->monochrome) {
+ img->planes[AOM_PLANE_U] = img->planes[AOM_PLANE_V] = nullptr;
+ }
+
+ EXPECT_EQ(aom_codec_control(&enc, AOME_SET_VALIDATE_INPUT_HBD,
+ check_input_range),
+ AOM_CODEC_OK);
+
+ EXPECT_EQ(aom_codec_encode(&enc, img, /*pts=*/0, /*duration=*/1,
+ /*flags=*/0),
+ check_input_range ? AOM_CODEC_INVALID_PARAM : AOM_CODEC_OK)
+ << "Error: " << aom_codec_error_detail(&enc)
+ << ", bitdepth: " << bitdepth << ", monochrome: " << img->monochrome
+ << ", check_input_range: " << check_input_range;
+
+ img->planes[AOM_PLANE_U] = u;
+ img->planes[AOM_PLANE_V] = v;
+ }
+
+ EXPECT_EQ(
+ aom_codec_encode(&enc, /*img=*/nullptr, /*pts=*/0, /*duration=*/0,
+ /*flags=*/0),
+ AOM_CODEC_OK);
+ EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+ }
+ }
+}
+#endif // CONFIG_AV1_HIGHBITDEPTH
+
void EncodeSetSFrameOnFirstFrame(aom_img_fmt fmt, aom_codec_flags_t flag) {
constexpr int kWidth = 2;
constexpr int kHeight = 128;