Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "ImageContainer.h"
#include "MediaData.h"
#include "MediaInfo.h"
#include "MediaResult.h"
#include "gtest/gtest.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/gfx/Types.h"
#include "nsIWidget.h"
#include "nsTArray.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using media::TimeUnit;
static void BuildI420Buffer(uint32_t aWidth, uint32_t aHeight,
nsTArray<uint8_t>& aStorage,
VideoData::YCbCrBuffer& aBuffer) {
uint32_t yLen = aWidth * aHeight;
uint32_t uvW = (aWidth + 1) / 2;
uint32_t uvH = (aHeight + 1) / 2;
uint32_t uvLen = uvW * uvH;
aStorage.SetLength(yLen + 2 * uvLen);
memset(aStorage.Elements(), 0x10, yLen);
memset(aStorage.Elements() + yLen, 0x80, 2 * uvLen);
aBuffer.mPlanes[0] = {aStorage.Elements(), aWidth, aHeight, aWidth, 0};
aBuffer.mPlanes[1] = {aStorage.Elements() + yLen, uvW, uvH, uvW, 0};
aBuffer.mPlanes[2] = {aStorage.Elements() + yLen + uvLen, uvW, uvH, uvW, 0};
aBuffer.mYUVColorSpace = YUVColorSpace::BT2020;
aBuffer.mChromaSubsampling = ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
}
// Runs SetVideoDataToImage with the given HDR transfer function and full
// HDRMetadata (Smpte2086 + ContentLightLevel), then asserts all attributes.
static void TestHDRTransferFunctionPropagation(TransferFunction aTF) {
const uint32_t w = 64, h = 48;
nsTArray<uint8_t> storage;
VideoData::YCbCrBuffer buf{};
BuildI420Buffer(w, h, storage, buf);
VideoInfo info;
info.mDisplay = IntSize(w, h);
info.mTransferFunction = Some(aTF);
gfx::Smpte2086Metadata smpte2086;
smpte2086.displayPrimaryRed = {0.708f, 0.292f};
smpte2086.displayPrimaryGreen = {0.170f, 0.797f};
smpte2086.displayPrimaryBlue = {0.131f, 0.046f};
smpte2086.whitePoint = {0.3127f, 0.3290f};
smpte2086.maxLuminance = 1000.0f;
smpte2086.minLuminance = 0.001f;
gfx::HDRMetadata hdrMetadata;
hdrMetadata.mSmpte2086 = Some(smpte2086);
hdrMetadata.mContentLightLevel = Some(gfx::ContentLightLevel{1000, 400});
info.mHDRMetadata = Some(hdrMetadata);
RefPtr<PlanarYCbCrImage> image =
new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
MediaResult rv = VideoData::SetVideoDataToImage(image, info, buf,
IntRect(0, 0, w, h), true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
const PlanarYCbCrData* data = image->GetData();
ASSERT_NE(data, nullptr);
EXPECT_EQ(data->mTransferFunction, aTF);
ASSERT_TRUE(data->mHDRMetadata.isSome());
ASSERT_TRUE(data->mHDRMetadata->mSmpte2086.isSome());
EXPECT_FLOAT_EQ(data->mHDRMetadata->mSmpte2086->maxLuminance, 1000.0f);
EXPECT_FLOAT_EQ(data->mHDRMetadata->mSmpte2086->minLuminance, 0.001f);
EXPECT_FLOAT_EQ(data->mHDRMetadata->mSmpte2086->displayPrimaryRed.x, 0.708f);
EXPECT_FLOAT_EQ(data->mHDRMetadata->mSmpte2086->whitePoint.y, 0.3290f);
ASSERT_TRUE(data->mHDRMetadata->mContentLightLevel.isSome());
EXPECT_EQ(data->mHDRMetadata->mContentLightLevel->maxContentLightLevel, 1000);
EXPECT_EQ(data->mHDRMetadata->mContentLightLevel->maxFrameAverageLightLevel,
400);
}
TEST(VideoData, SetVideoDataToImagePropagatesPQTransferFunction)
{
TestHDRTransferFunctionPropagation(TransferFunction::PQ);
}
TEST(VideoData, SetVideoDataToImagePropagatesHLGTransferFunction)
{
TestHDRTransferFunctionPropagation(TransferFunction::HLG);
}
// When VideoInfo has no TransferFunction, the BT709 default is preserved.
TEST(VideoData, SetVideoDataToImagePreservesBT709Default)
{
const uint32_t w = 64, h = 48;
nsTArray<uint8_t> storage;
VideoData::YCbCrBuffer buf{};
BuildI420Buffer(w, h, storage, buf);
VideoInfo info;
info.mDisplay = IntSize(w, h);
// mTransferFunction = Nothing() — default
RefPtr<PlanarYCbCrImage> image =
new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
MediaResult rv = VideoData::SetVideoDataToImage(image, info, buf,
IntRect(0, 0, w, h), true);
ASSERT_TRUE(NS_SUCCEEDED(rv));
const PlanarYCbCrData* data = image->GetData();
ASSERT_NE(data, nullptr);
EXPECT_EQ(data->mTransferFunction, TransferFunction::BT709);
EXPECT_EQ(data->mHDRMetadata, Nothing());
}
TEST(VideoData, StrideOrSizeMismatch)
{
constexpr int width = 32;
constexpr int height = 64;
constexpr int stride = width * 10;
VideoInfo info(width, height);
uint8_t buffer[stride * height] = {};
VideoData::YCbCrBuffer b;
VideoData::YCbCrBuffer::Plane alpha_plane;
b.mPlanes[0].mStride = alpha_plane.mStride = stride;
b.mPlanes[0].mHeight = alpha_plane.mHeight = height;
b.mPlanes[0].mWidth = alpha_plane.mWidth = width;
b.mPlanes[0].mSkip = alpha_plane.mSkip = 0;
b.mPlanes[0].mData = alpha_plane.mData = buffer;
b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
b.mPlanes[1].mStride = b.mPlanes[2].mStride = stride / 4;
b.mPlanes[1].mSkip = b.mPlanes[2].mSkip = 0;
b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = (b.mPlanes[0].mHeight + 1) / 2;
b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = (b.mPlanes[0].mWidth + 1) / 2;
// Use tail end of buffer.
b.mPlanes[1].mData = b.mPlanes[2].mData =
buffer + stride * height - b.mPlanes[1].mStride * b.mPlanes[1].mHeight;
b.mYUVColorSpace = gfx::YUVColorSpace::BT601;
b.mColorRange = gfx::ColorRange::FULL;
RefPtr imageContainer = new ImageContainer(
ImageUsageType::VideoFrameContainer, ImageContainer::ASYNCHRONOUS);
auto CreateVideoData = [&]() {
Result res = VideoData::CreateAndCopyData(
info, imageContainer, /*aOffset*/ 0, TimeUnit::Zero(),
TimeUnit::FromSeconds(1), b, alpha_plane, /*aKeyFrame*/ true,
TimeUnit::Zero(), info.ImageRect());
return res.unwrapOr(nullptr);
};
// Trigger gfx::GPUProcessManager::EnsureImageBridgeChild()
// for ImageContainer::CreateSharedRGBImage(),
// from VideoData::CreateAndCopyData().
// (ImageContainer::EnsureRecycleAllocatorForRDD() requires
// XRE_IsRDDProcess().)
RefPtr widget = nsIWidget::CreateHeadlessWidget();
widget->CreateCompositor();
// Check that VideoData can be created successfully from a simple
// combination of planes.
RefPtr v = CreateVideoData();
EXPECT_NE(v.get(), nullptr);
// Check that non-so-simple combinations do not cause crashes.
// At the time of writing this test, CreateAndCopyData() returns failure,
// but these combinations could be supported in the future, if desired.
{
// Differing alpha and luma plane strides
AutoRestore r(alpha_plane.mStride);
AutoRestore r2(alpha_plane.mData);
alpha_plane.mStride = width;
EXPECT_NE(alpha_plane.mStride, b.mPlanes[0].mStride);
// tail end of buffer
alpha_plane.mData = buffer + (stride - alpha_plane.mStride) * height;
v = CreateVideoData();
// Change to NE if this becomes supported.
EXPECT_EQ(v.get(), nullptr);
}
{
// Differing alpha and luma plane heights
AutoRestore r(alpha_plane.mHeight);
AutoRestore r2(alpha_plane.mData);
alpha_plane.mHeight = height / 2;
EXPECT_NE(alpha_plane.mHeight, b.mPlanes[0].mHeight);
alpha_plane.mData =
buffer + stride * height - alpha_plane.mStride * alpha_plane.mHeight;
v = CreateVideoData();
// Change to NE if this becomes supported.
EXPECT_EQ(v.get(), nullptr);
}
{
// Differing chroma plane strides
AutoRestore r(b.mPlanes[2].mStride);
AutoRestore r2(b.mPlanes[2].mData);
b.mPlanes[2].mStride = b.mPlanes[2].mWidth;
EXPECT_NE(b.mPlanes[1].mStride, b.mPlanes[2].mStride);
b.mPlanes[2].mData =
buffer + stride * height - b.mPlanes[2].mStride * b.mPlanes[2].mHeight;
v = CreateVideoData();
// Change to NE if this becomes supported.
EXPECT_EQ(v.get(), nullptr);
}
}