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 70c2dad..8d14b41 100644 --- a/aom/aomcx.h +++ b/aom/aomcx.h
@@ -1631,6 +1631,14 @@ */ 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(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 e726da6..120f6f7 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -246,6 +246,7 @@ 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 @@ -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 @@ -1, // kf_max_pyr_height 0, // sb_qp_sweep AOM_SCREEN_DETECTION_STANDARD, + 1, // validate_input_hbd }; #endif @@ -1017,6 +1020,31 @@ } #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 @@ 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 @@ 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 34d92fc..7f43a72 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 @@ } } +#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;