/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "ImageContainer.h" #include "MediaData.h" #include "VideoUtils.h" #include "gtest/gtest.h" #include "mozilla/DefineEnum.h" #include "mozilla/UniquePtr.h" using namespace mozilla; using namespace mozilla::gfx; MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING(PixFormatParam, (I420, I422, I444)) class QuantizableBufferTest : public testing::TestWithParam { public: using TenBit = uint16_t; static constexpr uint32_t kVGAWidth = 640; static constexpr uint32_t kVGAHeight = 480; template struct TestBuffer { TestBuffer(const uint32_t aWidth, const uint32_t aHeight, const PixFormatParam aPixFormat, const ColorDepth aColorDepth, UniquePtr>&& aSourceData = UniquePtr>{}) { EXPECT_NE(aColorDepth, ColorDepth::COLOR_8); const uint32_t yWidth{aWidth}; const uint32_t yHeight{aHeight}; auto toCbCr = [&aPixFormat](uint32_t aSize, bool aIsWidth) -> uint32_t { uint64_t size{aSize}; if (aPixFormat == PixFormatParam::I420 || (aPixFormat == PixFormatParam::I422 && aIsWidth)) { size = (size + 1) / 2; } EXPECT_TRUE(CheckedUint32(size).isValid()); return static_cast(size); }; const uint32_t cbcrWidth{toCbCr(aWidth, true)}; const uint32_t cbcrHeight{toCbCr(aHeight, false)}; mBuffer.mPlanes[0].mWidth = yWidth; mBuffer.mPlanes[0].mHeight = yHeight; mBuffer.mPlanes[1].mWidth = mBuffer.mPlanes[2].mWidth = cbcrWidth; mBuffer.mPlanes[1].mHeight = mBuffer.mPlanes[2].mHeight = cbcrHeight; auto toStride = [](uint32_t aWidth) -> uint32_t { CheckedUint32 x{aWidth}; x *= sizeof(DataType); // -> bytes. // Invalid value (stride should always >= 2 * width) when out of range. return x.isValid() ? x.value() : 1; }; mBuffer.mPlanes[0].mStride = toStride(yWidth); mBuffer.mPlanes[1].mStride = mBuffer.mPlanes[2].mStride = toStride(cbcrWidth); // If not supplied, allocate buffer based on provided width/height, or // create an empty one for invalid sizes. (To8BitPerChannel() will reject // before accessing buffer so it should be safe.) CheckedInt checkedYLength = CheckedInt(mBuffer.mPlanes[0].mStride) * yHeight; CheckedInt checkedCbLength = CheckedInt(mBuffer.mPlanes[1].mStride) * cbcrHeight; CheckedInt bufferSize = checkedYLength + checkedCbLength * sizeof(DataType); mData = aSourceData ? std::move(aSourceData) : MakeUnique>( bufferSize.isValid() ? bufferSize.value() : 0); if (mData->Data()) { // Use checked arithmetic to ensure no memory address overflow. CheckedInt checkedAddr{ reinterpret_cast(mData->Data())}; // Y mBuffer.mPlanes[0].mData = reinterpret_cast(checkedAddr.value()); checkedAddr += checkedYLength * sizeof(DataType); // Cb mBuffer.mPlanes[1].mData = checkedAddr.isValid() ? reinterpret_cast(checkedAddr.value()) : reinterpret_cast(mData->Data()); checkedAddr += checkedCbLength * sizeof(DataType); // Cr mBuffer.mPlanes[2].mData = checkedAddr.isValid() ? reinterpret_cast(checkedAddr.value()) : reinterpret_cast(mData->Data()); } mBuffer.mPlanes[0].mSkip = mBuffer.mPlanes[1].mSkip = mBuffer.mPlanes[2].mSkip = 0; mBuffer.mYUVColorSpace = DefaultColorSpace(IntSize{aWidth, aHeight}); mBuffer.mColorPrimaries = ColorSpace2::Display; mBuffer.mColorDepth = aColorDepth; mBuffer.mChromaSubsampling = [&aPixFormat]() -> ChromaSubsampling { switch (aPixFormat) { case PixFormatParam::I420: return ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; case PixFormatParam::I422: return ChromaSubsampling::HALF_WIDTH; case PixFormatParam::I444: return ChromaSubsampling::FULL; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(); }(); } VideoData::QuantizableBuffer mBuffer; private: UniquePtr> mData; }; RefPtr mRecycleBin = MakeRefPtr(); }; INSTANTIATE_TEST_SUITE_P( Format, QuantizableBufferTest, ::testing::Values(PixFormatParam::I420, PixFormatParam::I422, PixFormatParam::I444), [](const ::testing::TestParamInfo& info) { return std::string(EnumValueToString(info.param)); }); TEST_P(QuantizableBufferTest, VariousSizes) { TestBuffer vga{kVGAWidth, kVGAHeight, GetParam(), ColorDepth::COLOR_10}; EXPECT_EQ(vga.mBuffer.To8BitPerChannel(mRecycleBin), NS_OK); TestBuffer oddCbCrWidth{1, kVGAHeight, GetParam(), ColorDepth::COLOR_10}; EXPECT_EQ(oddCbCrWidth.mBuffer.To8BitPerChannel(mRecycleBin), NS_OK); TestBuffer oddCbCrHeight{kVGAWidth, 1, GetParam(), ColorDepth::COLOR_10}; EXPECT_EQ(oddCbCrHeight.mBuffer.To8BitPerChannel(mRecycleBin), NS_OK); } TEST_P(QuantizableBufferTest, InvalidSizes) { TestBuffer zeroWidth{0, kVGAHeight, GetParam(), ColorDepth::COLOR_10, MakeUnique>(kVGAHeight)}; EXPECT_EQ(zeroWidth.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); TestBuffer zeroHeight{kVGAWidth, 0, GetParam(), ColorDepth::COLOR_10, MakeUnique>(kVGAWidth)}; EXPECT_EQ(zeroHeight.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); TestBuffer invalidWidth{std::numeric_limits::max(), kVGAHeight, GetParam(), ColorDepth::COLOR_10, MakeUnique>(kVGAHeight)}; EXPECT_EQ(invalidWidth.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); TestBuffer invalidHeight{kVGAWidth, std::numeric_limits::max(), GetParam(), ColorDepth::COLOR_10, MakeUnique>(kVGAWidth)}; EXPECT_EQ(invalidHeight.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); TestBuffer invalidDestLength{ std::numeric_limits::max(), std::numeric_limits::max(), GetParam(), ColorDepth::COLOR_10, MakeUnique>(kVGAWidth * kVGAHeight)}; EXPECT_EQ(invalidDestLength.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); }; TEST_P(QuantizableBufferTest, NoData) { TestBuffer noData{kVGAWidth, kVGAHeight, GetParam(), ColorDepth::COLOR_10, MakeUnique>()}; EXPECT_EQ(noData.mBuffer.To8BitPerChannel(mRecycleBin), NS_ERROR_ILLEGAL_VALUE); }