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 3b71086..7d0e9fc 100644 --- a/aom/aom_image.h +++ b/aom/aom_image.h
@@ -229,8 +229,13 @@ /* 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 5abd959..443f62c 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -989,6 +989,10 @@ 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 af86bb8..06e4048 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 @@ 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;