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;