blob: dcc045c3239ba1ca3dc997b9c66858444446154f [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/mac/vt_config_util.h"
#include <CoreMedia/CoreMedia.h>
#include "base/containers/span.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/sdk_forward_declarations.h"
#include "base/strings/sys_string_conversions.h"
#include "media/base/mac/color_space_util_mac.h"
#include "media/formats/mp4/box_definitions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::string GetStrValue(CFDictionaryRef dict, CFStringRef key) {
return base::SysCFStringRefToUTF8(
base::mac::CFCastStrict<CFStringRef>(CFDictionaryGetValue(dict, key)));
}
CFStringRef GetCFStrValue(CFDictionaryRef dict, CFStringRef key) {
return base::mac::CFCastStrict<CFStringRef>(CFDictionaryGetValue(dict, key));
}
int GetIntValue(CFDictionaryRef dict, CFStringRef key) {
CFNumberRef value =
base::mac::CFCastStrict<CFNumberRef>(CFDictionaryGetValue(dict, key));
int result;
return CFNumberGetValue(value, kCFNumberIntType, &result) ? result : -1;
}
bool GetBoolValue(CFDictionaryRef dict, CFStringRef key) {
return CFBooleanGetValue(
base::mac::CFCastStrict<CFBooleanRef>(CFDictionaryGetValue(dict, key)));
}
base::span<const uint8_t> GetDataValue(CFDictionaryRef dict, CFStringRef key) {
CFDataRef data =
base::mac::CFCastStrict<CFDataRef>(CFDictionaryGetValue(dict, key));
return data ? base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(data)),
CFDataGetLength(data))
: base::span<const uint8_t>();
}
base::span<const uint8_t> GetNestedDataValue(CFDictionaryRef dict,
CFStringRef key1,
CFStringRef key2) {
CFDictionaryRef nested_dict = base::mac::CFCastStrict<CFDictionaryRef>(
CFDictionaryGetValue(dict, key1));
return GetDataValue(nested_dict, key2);
}
base::ScopedCFTypeRef<CVImageBufferRef> CreateCVImageBuffer(
media::VideoColorSpace cs) {
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_H264, media::H264PROFILE_MAIN, cs, gfx::HDRMetadata()));
base::ScopedCFTypeRef<CVImageBufferRef> image_buffer;
OSStatus err =
CVPixelBufferCreate(kCFAllocatorDefault, 16, 16,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
nullptr, image_buffer.InitializeInto());
if (err != noErr) {
EXPECT_EQ(err, noErr);
return base::ScopedCFTypeRef<CVImageBufferRef>();
}
CVBufferSetAttachments(image_buffer.get(), fmt,
kCVAttachmentMode_ShouldNotPropagate);
return image_buffer;
}
base::ScopedCFTypeRef<CMFormatDescriptionRef> CreateFormatDescription(
CFStringRef primaries,
CFStringRef transfer,
CFStringRef matrix) {
base::ScopedCFTypeRef<CFMutableDictionaryRef> extensions(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (primaries) {
CFDictionarySetValue(
extensions, kCMFormatDescriptionExtension_ColorPrimaries, primaries);
}
if (transfer) {
CFDictionarySetValue(
extensions, kCMFormatDescriptionExtension_TransferFunction, transfer);
}
if (matrix) {
CFDictionarySetValue(extensions, kCMFormatDescriptionExtension_YCbCrMatrix,
matrix);
}
base::ScopedCFTypeRef<CMFormatDescriptionRef> result;
CMFormatDescriptionCreate(nullptr, kCMMediaType_Video,
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
extensions.get(), result.InitializeInto());
return result;
}
gfx::ColorSpace ToBT709_APPLE(gfx::ColorSpace cs) {
return gfx::ColorSpace(cs.GetPrimaryID(),
gfx::ColorSpace::TransferID::BT709_APPLE,
cs.GetMatrixID(), cs.GetRangeID());
}
void AssertHasEmptyHDRMetadata(CFDictionaryRef fmt) {
if (__builtin_available(macos 10.13, *)) {
// We constructed with an empty HDRMetadata, so all values should be zero.
auto mdcv = GetDataValue(
fmt, kCMFormatDescriptionExtension_MasteringDisplayColorVolume);
ASSERT_EQ(24u, mdcv.size());
for (size_t i = 0; i < mdcv.size(); ++i)
EXPECT_EQ(0u, mdcv[i]);
auto clli =
GetDataValue(fmt, kCMFormatDescriptionExtension_ContentLightLevelInfo);
ASSERT_EQ(4u, clli.size());
for (size_t i = 0; i < clli.size(); ++i)
EXPECT_EQ(0u, clli[i]);
}
}
constexpr char kBitDepthKey[] = "BitsPerComponent";
constexpr char kVpccKey[] = "vpcC";
} // namespace
namespace media {
TEST(VTConfigUtil, CreateFormatExtensions_H264_BT709) {
base::ScopedCFTypeRef<CFDictionaryRef> fmt(
CreateFormatExtensions(kCMVideoCodecType_H264, H264PROFILE_MAIN,
VideoColorSpace::REC709(), absl::nullopt));
EXPECT_EQ("avc1", GetStrValue(fmt, kCMFormatDescriptionExtension_FormatName));
EXPECT_EQ(24, GetIntValue(fmt, kCMFormatDescriptionExtension_Depth));
EXPECT_EQ(kCMFormatDescriptionColorPrimaries_ITU_R_709_2,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_ColorPrimaries));
EXPECT_EQ(kCMFormatDescriptionTransferFunction_ITU_R_709_2,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_TransferFunction));
EXPECT_EQ(kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_YCbCrMatrix));
EXPECT_FALSE(GetBoolValue(fmt, kCMFormatDescriptionExtension_FullRangeVideo));
if (__builtin_available(macos 10.13, *)) {
EXPECT_TRUE(
GetDataValue(fmt,
kCMFormatDescriptionExtension_MasteringDisplayColorVolume)
.empty());
EXPECT_TRUE(
GetDataValue(fmt, kCMFormatDescriptionExtension_ContentLightLevelInfo)
.empty());
}
}
TEST(VTConfigUtil, CreateFormatExtensions_H264_BT2020_PQ) {
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_H264, H264PROFILE_MAIN,
VideoColorSpace(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::SMPTEST2084,
VideoColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::FULL),
gfx::HDRMetadata()));
EXPECT_EQ("avc1", GetStrValue(fmt, kCMFormatDescriptionExtension_FormatName));
EXPECT_EQ(24, GetIntValue(fmt, kCMFormatDescriptionExtension_Depth));
if (__builtin_available(macos 10.13, *)) {
EXPECT_EQ(kCMFormatDescriptionColorPrimaries_ITU_R_2020,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_ColorPrimaries));
EXPECT_EQ(
kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_TransferFunction));
EXPECT_EQ(kCMFormatDescriptionYCbCrMatrix_ITU_R_2020,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_YCbCrMatrix));
}
EXPECT_TRUE(GetBoolValue(fmt, kCMFormatDescriptionExtension_FullRangeVideo));
AssertHasEmptyHDRMetadata(fmt);
}
TEST(VTConfigUtil, CreateFormatExtensions_H264_BT2020_HLG) {
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_H264, H264PROFILE_MAIN,
VideoColorSpace(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::ARIB_STD_B67,
VideoColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::FULL),
gfx::HDRMetadata()));
EXPECT_EQ("avc1", GetStrValue(fmt, kCMFormatDescriptionExtension_FormatName));
EXPECT_EQ(24, GetIntValue(fmt, kCMFormatDescriptionExtension_Depth));
if (__builtin_available(macos 10.13, *)) {
EXPECT_EQ(kCMFormatDescriptionColorPrimaries_ITU_R_2020,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_ColorPrimaries));
EXPECT_EQ(
kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_TransferFunction));
EXPECT_EQ(kCMFormatDescriptionYCbCrMatrix_ITU_R_2020,
GetCFStrValue(fmt, kCMFormatDescriptionExtension_YCbCrMatrix));
}
EXPECT_TRUE(GetBoolValue(fmt, kCMFormatDescriptionExtension_FullRangeVideo));
AssertHasEmptyHDRMetadata(fmt);
}
TEST(VTConfigUtil, CreateFormatExtensions_HDRMetadata) {
// Values from real YouTube HDR content.
gfx::HDRMetadata hdr_meta;
hdr_meta.max_content_light_level = 1000;
hdr_meta.max_frame_average_light_level = 600;
auto& cv_metadata = hdr_meta.color_volume_metadata;
cv_metadata.luminance_min = 0;
cv_metadata.luminance_max = 1000;
cv_metadata.primary_r = gfx::PointF(0.68, 0.32);
cv_metadata.primary_g = gfx::PointF(0.2649, 0.69);
cv_metadata.primary_b = gfx::PointF(0.15, 0.06);
cv_metadata.white_point = gfx::PointF(0.3127, 0.3290);
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_H264, H264PROFILE_MAIN,
VideoColorSpace(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::SMPTEST2084,
VideoColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::FULL),
hdr_meta));
if (__builtin_available(macos 10.13, *)) {
{
auto mdcv = GetDataValue(
fmt, kCMFormatDescriptionExtension_MasteringDisplayColorVolume);
ASSERT_EQ(24u, mdcv.size());
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(mdcv.data(), mdcv.size(),
nullptr));
mp4::MasteringDisplayColorVolume mdcv_box;
ASSERT_TRUE(mdcv_box.Parse(box_reader.get()));
EXPECT_EQ(mdcv_box.display_primaries_gx, cv_metadata.primary_g.x());
EXPECT_EQ(mdcv_box.display_primaries_gy, cv_metadata.primary_g.y());
EXPECT_EQ(mdcv_box.display_primaries_bx, cv_metadata.primary_b.x());
EXPECT_EQ(mdcv_box.display_primaries_by, cv_metadata.primary_b.y());
EXPECT_EQ(mdcv_box.display_primaries_rx, cv_metadata.primary_r.x());
EXPECT_EQ(mdcv_box.display_primaries_ry, cv_metadata.primary_r.y());
EXPECT_EQ(mdcv_box.white_point_x, cv_metadata.white_point.x());
EXPECT_EQ(mdcv_box.white_point_y, cv_metadata.white_point.y());
EXPECT_EQ(mdcv_box.max_display_mastering_luminance,
cv_metadata.luminance_max);
EXPECT_EQ(mdcv_box.min_display_mastering_luminance,
cv_metadata.luminance_min);
}
{
auto clli = GetDataValue(
fmt, kCMFormatDescriptionExtension_ContentLightLevelInfo);
ASSERT_EQ(4u, clli.size());
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(clli.data(), clli.size(),
nullptr));
mp4::ContentLightLevelInformation clli_box;
ASSERT_TRUE(clli_box.Parse(box_reader.get()));
EXPECT_EQ(clli_box.max_content_light_level,
hdr_meta.max_content_light_level);
EXPECT_EQ(clli_box.max_pic_average_light_level,
hdr_meta.max_frame_average_light_level);
}
}
}
TEST(VTConfigUtil, CreateFormatExtensions_VP9Profile0) {
constexpr VideoCodecProfile kTestProfile = VP9PROFILE_PROFILE0;
const auto kTestColorSpace = VideoColorSpace::REC709();
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_VP9, kTestProfile, kTestColorSpace, absl::nullopt));
EXPECT_EQ(8, GetIntValue(fmt, base::SysUTF8ToCFStringRef(kBitDepthKey)));
auto vpcc = GetNestedDataValue(
fmt, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms,
base::SysUTF8ToCFStringRef(kVpccKey));
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(vpcc.data(), vpcc.size(),
nullptr));
mp4::VPCodecConfigurationRecord vpcc_box;
ASSERT_TRUE(vpcc_box.Parse(box_reader.get()));
ASSERT_EQ(kTestProfile, vpcc_box.profile);
ASSERT_EQ(kTestColorSpace, vpcc_box.color_space);
}
TEST(VTConfigUtil, CreateFormatExtensions_VP9Profile2) {
constexpr VideoCodecProfile kTestProfile = VP9PROFILE_PROFILE2;
const VideoColorSpace kTestColorSpace(
VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::SMPTEST2084,
VideoColorSpace::MatrixID::BT2020_NCL, gfx::ColorSpace::RangeID::LIMITED);
base::ScopedCFTypeRef<CFDictionaryRef> fmt(CreateFormatExtensions(
kCMVideoCodecType_VP9, kTestProfile, kTestColorSpace, absl::nullopt));
EXPECT_EQ(10, GetIntValue(fmt, base::SysUTF8ToCFStringRef(kBitDepthKey)));
auto vpcc = GetNestedDataValue(
fmt, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms,
base::SysUTF8ToCFStringRef(kVpccKey));
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(vpcc.data(), vpcc.size(),
nullptr));
mp4::VPCodecConfigurationRecord vpcc_box;
ASSERT_TRUE(vpcc_box.Parse(box_reader.get()));
ASSERT_EQ(kTestProfile, vpcc_box.profile);
ASSERT_EQ(kTestColorSpace, vpcc_box.color_space);
}
TEST(VTConfigUtil, GetImageBufferColorSpace_BT601) {
auto cs = VideoColorSpace::REC601();
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
// macOS doesn't have a SMPTE170M transfer function, apps are supposed to use
// kCMFormatDescriptionTransferFunction_ITU_R_709_2 instead for SDR content.
cs.primaries = VideoColorSpace::PrimaryID::SMPTE240M;
auto expected_cs = ToBT709_APPLE(cs.ToGfxColorSpace());
EXPECT_EQ(expected_cs, GetImageBufferColorSpace(image_buffer));
}
TEST(VTConfigUtil, GetImageBufferColorSpace_BT709) {
auto cs = VideoColorSpace::REC709();
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
// macOS returns a special BT709_APPLE transfer function since it doesn't use
// the same gamma level as is standardized.
auto expected_cs = ToBT709_APPLE(cs.ToGfxColorSpace());
EXPECT_EQ(expected_cs, GetImageBufferColorSpace(image_buffer));
}
TEST(VTConfigUtil, GetImageBufferColorSpace_GAMMA22) {
auto cs = VideoColorSpace(VideoColorSpace::PrimaryID::SMPTE240M,
VideoColorSpace::TransferID::GAMMA22,
VideoColorSpace::MatrixID::SMPTE240M,
gfx::ColorSpace::RangeID::LIMITED);
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
EXPECT_EQ(cs.ToGfxColorSpace(), GetImageBufferColorSpace(image_buffer));
}
TEST(VTConfigUtil, GetImageBufferColorSpace_GAMMA28) {
auto cs = VideoColorSpace(VideoColorSpace::PrimaryID::SMPTE240M,
VideoColorSpace::TransferID::GAMMA28,
VideoColorSpace::MatrixID::SMPTE240M,
gfx::ColorSpace::RangeID::LIMITED);
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
EXPECT_EQ(cs.ToGfxColorSpace(), GetImageBufferColorSpace(image_buffer));
}
TEST(VTConfigUtil, GetImageBufferColorSpace_BT2020_PQ) {
auto cs = VideoColorSpace(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::SMPTEST2084,
VideoColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::LIMITED);
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
auto image_buffer_cs = GetImageBufferColorSpace(image_buffer);
// When BT.2020 is unavailable the default should be BT.709.
if (base::mac::IsAtLeastOS10_13()) {
EXPECT_EQ(cs.ToGfxColorSpace(), image_buffer_cs);
} else {
// 10.12 doesn't have HDR transfer functions.
cs.transfer = VideoColorSpace::TransferID::BT709;
EXPECT_EQ(cs.ToGfxColorSpace(), image_buffer_cs);
}
}
TEST(VTConfigUtil, GetImageBufferColorSpace_BT2020_HLG) {
auto cs = VideoColorSpace(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::ARIB_STD_B67,
VideoColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::LIMITED);
auto image_buffer = CreateCVImageBuffer(cs);
ASSERT_TRUE(image_buffer);
auto image_buffer_cs = GetImageBufferColorSpace(image_buffer);
// When BT.2020 is unavailable the default should be BT.709.
if (base::mac::IsAtLeastOS10_13()) {
EXPECT_EQ(cs.ToGfxColorSpace(), image_buffer_cs);
} else {
// 10.12 doesn't have HDR transfer functions.
cs.transfer = VideoColorSpace::TransferID::BT709;
EXPECT_EQ(cs.ToGfxColorSpace(), image_buffer_cs);
}
}
TEST(VTConfigUtil, FormatDescriptionInvalid) {
auto format_descriptor =
CreateFormatDescription(CFSTR("Cows"), CFSTR("Go"), CFSTR("Moo"));
ASSERT_TRUE(format_descriptor);
auto cs = GetFormatDescriptionColorSpace(format_descriptor);
EXPECT_EQ(gfx::ColorSpace::CreateREC709(), cs);
}
TEST(VTConfigUtil, FormatDescriptionBT709) {
auto format_descriptor =
CreateFormatDescription(kCMFormatDescriptionColorPrimaries_ITU_R_709_2,
kCMFormatDescriptionTransferFunction_ITU_R_709_2,
kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2);
ASSERT_TRUE(format_descriptor);
auto cs = GetFormatDescriptionColorSpace(format_descriptor);
EXPECT_EQ(ToBT709_APPLE(gfx::ColorSpace::CreateREC709()), cs);
}
} // namespace media