Commit a749f280b3 for aom
commit a749f280b37306a2b64557ffaf6f47a4e14e3f42
Author: James Zern <jzern@google.com>
Date: Sat Mar 14 18:21:45 2026 -0700
av1 encoder: reject unequal UV strides
The `aom_image_t` from the external API is converted to an internal
`YV12_BUFFER_CONFIG`, which assumes U and V strides are the same. This
avoids a potential crash or corrupted chroma if they differ.
Ported from libvpx:
915643b94 vp[89] encoders: reject unequal UV strides
Bug: 492213293
Change-Id: I3c878bdd330a8f7f838ac3bce8b66201c49f537d
diff --git a/aom/aom_image.h b/aom/aom_image.h
index 3b71086f61..7d0e9fcf3a 100644
--- a/aom/aom_image.h
+++ b/aom/aom_image.h
@@ -229,8 +229,13 @@ typedef struct aom_image {
/* planes[AOM_PLANE_V] = NULL and stride[AOM_PLANE_V] = 0 when fmt ==
* AOM_IMG_FMT_NV12 */
unsigned char *planes[3]; /**< pointer to the top left pixel for each plane */
- int stride[3]; /**< stride between rows for each plane */
- size_t sz; /**< data size */
+ /*!Stride between rows for each plane
+ *
+ * \note With planar formats, \c stride[AOM_PLANE_U] must be the same as \c
+ * stride[AOM_PLANE_V].
+ */
+ int stride[3];
+ size_t sz; /**< data size */
int bps; /**< bits per sample (for packed formats) */
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 5abd959d91..443f62c261 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -989,6 +989,10 @@ static aom_codec_err_t validate_img(aom_codec_alg_priv_t *ctx,
if (img->d_w != ctx->cfg.g_w || img->d_h != ctx->cfg.g_h)
ERROR("Image size must match encoder init configuration size");
+ assert(img->fmt & AOM_IMG_FMT_PLANAR);
+ if (!ctx->cfg.monochrome && img->fmt != AOM_IMG_FMT_NV12 &&
+ img->stride[AOM_PLANE_U] != img->stride[AOM_PLANE_V])
+ ERROR("Image U/V strides must match");
// 6.4.2 Color config semantics
// If matrix_coefficients is equal to MC_IDENTITY, it is a requirement of
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index af86bb8315..06e4048898 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -9,12 +9,14 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+#include <array>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
+#include <memory>
#include <tuple>
#include "gtest/gtest.h"
@@ -190,6 +192,107 @@ TEST(EncodeAPI, InvalidControlId) {
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
}
+TEST(EncodeAPI, InvalidUVStrides) {
+ static constexpr std::array<aom_img_fmt_t, 11> kAv1ImageFormats = {
+ AOM_IMG_FMT_YV12, AOM_IMG_FMT_I420, AOM_IMG_FMT_AOMYV12,
+ AOM_IMG_FMT_AOMI420, AOM_IMG_FMT_I422, AOM_IMG_FMT_I444,
+ AOM_IMG_FMT_NV12, AOM_IMG_FMT_I42016, AOM_IMG_FMT_YV1216,
+ AOM_IMG_FMT_I42216, AOM_IMG_FMT_I44416
+ };
+ struct UVStride {
+ int u_stride;
+ int v_stride;
+ };
+ constexpr int kWidth = 64;
+ constexpr int kHeight = 64;
+ static constexpr std::array<UVStride, 4> kUVStrides = {
+ UVStride{ kWidth, kWidth - 1 }, UVStride{ kWidth, kWidth + 1 },
+ UVStride{ kWidth - 1, kWidth }, UVStride{ kWidth + 1, kWidth }
+ };
+ aom_image_t img;
+ aom_codec_ctx_t enc;
+ aom_codec_enc_cfg_t cfg;
+ // Allocate a buffer large enough for a non-subsampled, high bitdepth image.
+ auto buf = std::make_unique<uint8_t[]>(kWidth * kHeight * 3 * 2);
+
+ ASSERT_EQ(aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg,
+ /*usage=*/AOM_USAGE_REALTIME),
+ AOM_CODEC_OK);
+
+ for (const auto img_fmt : kAv1ImageFormats) {
+ const bool high_bit_depth =
+ (img_fmt & AOM_IMG_FMT_HIGHBITDEPTH) == AOM_IMG_FMT_HIGHBITDEPTH;
+ if (high_bit_depth && !(aom_codec_get_caps(aom_codec_av1_cx()) &
+ AOM_CODEC_CAP_HIGHBITDEPTH)) {
+ break;
+ }
+ ASSERT_EQ(aom_img_wrap(&img, img_fmt, kWidth, kHeight, /*stride_align=*/1,
+ buf.get()),
+ &img)
+ << "Unable to wrap aom_image for format: " << img_fmt;
+ const bool is_444 = (img.x_chroma_shift == 0 && img.y_chroma_shift == 0);
+ const bool is_422 = (img.x_chroma_shift == 1 && img.y_chroma_shift == 0);
+ // 4:4:4 is allowed in profile 1, 4:2:2 in profile 2.
+ cfg.g_profile = is_444 + 2 * is_422;
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+ cfg.g_bit_depth = high_bit_depth ? AOM_BITS_10 : AOM_BITS_8;
+ // Monochrome is not allowed in profile 1.
+ for (cfg.monochrome = 0; cfg.monochrome <= (cfg.g_profile != 1);
+ ++cfg.monochrome) {
+ ASSERT_EQ(
+ aom_codec_enc_init(&enc, aom_codec_av1_cx(), &cfg,
+ high_bit_depth ? AOM_CODEC_USE_HIGHBITDEPTH : 0),
+ AOM_CODEC_OK)
+ << " high_bit_depth: " << high_bit_depth;
+
+ for (const auto uv_stride : kUVStrides) {
+ const UVStride orig = { img.stride[AOM_PLANE_U],
+ img.stride[AOM_PLANE_V] };
+ img.stride[AOM_PLANE_U] = uv_stride.u_stride;
+ img.stride[AOM_PLANE_V] =
+ (img_fmt == AOM_IMG_FMT_NV12) ? 0 : uv_stride.v_stride;
+ img.monochrome = cfg.monochrome;
+ // Monochrome should ignore the U and V planes and NV12 only sets one
+ // stride value, they should always succeed. The AOM* aom_img_fmt_t
+ // variants are unsupported by the encoder.
+ aom_codec_err_t expected_err =
+ ((cfg.monochrome && img_fmt != AOM_IMG_FMT_AOMYV12 &&
+ img_fmt != AOM_IMG_FMT_AOMI420) ||
+ img_fmt == AOM_IMG_FMT_NV12)
+ ? AOM_CODEC_OK
+ : AOM_CODEC_INVALID_PARAM;
+ EXPECT_EQ(aom_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
+ /*flags=*/0),
+ expected_err)
+ << "Error: " << aom_codec_error_detail(&enc)
+ << ", format: " << img_fmt << ", U stride: " << uv_stride.u_stride
+ << ", V stride: " << uv_stride.v_stride;
+
+ // Ensure the encoder can recover when given valid strides.
+ img.stride[AOM_PLANE_U] = orig.u_stride;
+ img.stride[AOM_PLANE_V] = orig.v_stride;
+ expected_err =
+ (img_fmt == AOM_IMG_FMT_AOMYV12 || img_fmt == AOM_IMG_FMT_AOMI420)
+ ? AOM_CODEC_INVALID_PARAM
+ : AOM_CODEC_OK;
+ EXPECT_EQ(aom_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
+ /*flags=*/0),
+ expected_err)
+ << "Error: " << aom_codec_error_detail(&enc)
+ << ", format: " << img_fmt << ", U stride: " << orig.u_stride
+ << ", V stride: " << orig.v_stride;
+ }
+
+ 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);
+ }
+ }
+}
+
void EncodeSetSFrameOnFirstFrame(aom_img_fmt fmt, aom_codec_flags_t flag) {
constexpr int kWidth = 2;
constexpr int kHeight = 128;