[go: nahoru, domu]

blob: a9d3afd15201ad7cca5037d2d8af54871e1f2a66 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// 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/apple/foundation_util.h"
#include "base/containers/span.h"
#include "base/mac/mac_util.h"
#include "base/numerics/safe_conversions.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"
#include "ui/gfx/hdr_metadata_mac.h"
namespace {
std::string GetStrValue(CFDictionaryRef dict, CFStringRef key) {
return base::SysCFStringRefToUTF8(
base::apple::CFCastStrict<CFStringRef>(CFDictionaryGetValue(dict, key)));
}
CFStringRef GetCFStrValue(CFDictionaryRef dict, CFStringRef key) {
return base::apple::CFCastStrict<CFStringRef>(
CFDictionaryGetValue(dict, key));
}
int GetIntValue(CFDictionaryRef dict, CFStringRef key) {
CFNumberRef value =
base::apple::CFCastStrict<CFNumberRef>(CFDictionaryGetValue(dict, key));
int result;
return CFNumberGetValue(value, kCFNumberIntType, &result) ? result : -1;
}
bool GetBoolValue(CFDictionaryRef dict, CFStringRef key) {
return CFBooleanGetValue(
base::apple::CFCastStrict<CFBooleanRef>(CFDictionaryGetValue(dict, key)));
}
base::span<const uint8_t> GetDataValue(CFDictionaryRef dict, CFStringRef key) {
CFDataRef data =
base::apple::CFCastStrict<CFDataRef>(CFDictionaryGetValue(dict, key));
return data ? base::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(data)),
base::checked_cast<size_t>(CFDataGetLength(data)))
: base::span<const uint8_t>();
}
base::span<const uint8_t> GetNestedDataValue(CFDictionaryRef dict,
CFStringRef key1,
CFStringRef key2) {
CFDictionaryRef nested_dict = base::apple::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 AssertHasDefaultHDRMetadata(CFDictionaryRef fmt) {
// We constructed with an invalid HDRMetadata, so all values should be
// overridden to the default.
auto mdcv_expected = gfx::GenerateMasteringDisplayColorVolume(absl::nullopt);
auto clli_expected = gfx::GenerateContentLightLevelInfo(absl::nullopt);
auto mdcv = GetDataValue(
fmt, kCMFormatDescriptionExtension_MasteringDisplayColorVolume);
ASSERT_EQ(24u, mdcv.size());
ASSERT_EQ(24u, CFDataGetLength(mdcv_expected));
EXPECT_EQ(0, memcmp(mdcv.data(), CFDataGetBytePtr(mdcv_expected), 24u));
auto clli =
GetDataValue(fmt, kCMFormatDescriptionExtension_ContentLightLevelInfo);
ASSERT_EQ(0u, clli.size());
}
void AssertHasNoHDRMetadata(CFDictionaryRef fmt) {
auto mdcv = GetDataValue(
fmt, kCMFormatDescriptionExtension_MasteringDisplayColorVolume);
auto clli =
GetDataValue(fmt, kCMFormatDescriptionExtension_ContentLightLevelInfo);
EXPECT_TRUE(mdcv.empty());
EXPECT_TRUE(clli.empty());
}
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));
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));
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));
AssertHasDefaultHDRMetadata(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));
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));
AssertHasNoHDRMetadata(fmt);
}
TEST(VTConfigUtil, CreateFormatExtensions_HDRMetadata) {
// Values from real YouTube HDR content.
gfx::HDRMetadata hdr_meta;
hdr_meta.cta_861_3 = gfx::HdrMetadataCta861_3(1000, 600);
hdr_meta.smpte_st_2086 = gfx::HdrMetadataSmpteSt2086(
{0.6800f, 0.3200f, 0.2649f, 0.6900f, 0.1500f, 0.0600f, 0.3127f, 0.3290f},
/*luminance_max=*/1000,
/*luminance_min=*/0);
const auto& cv_metadata = hdr_meta.smpte_st_2086.value();
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);
{
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.primaries.fGX);
EXPECT_EQ(mdcv_box.display_primaries_gy, cv_metadata.primaries.fGY);
EXPECT_EQ(mdcv_box.display_primaries_bx, cv_metadata.primaries.fBX);
EXPECT_EQ(mdcv_box.display_primaries_by, cv_metadata.primaries.fBY);
EXPECT_EQ(mdcv_box.display_primaries_rx, cv_metadata.primaries.fRX);
EXPECT_EQ(mdcv_box.display_primaries_ry, cv_metadata.primaries.fRY);
EXPECT_EQ(mdcv_box.white_point_x, cv_metadata.primaries.fWX);
EXPECT_EQ(mdcv_box.white_point_y, cv_metadata.primaries.fWY);
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.cta_861_3->max_content_light_level);
EXPECT_EQ(clli_box.max_pic_average_light_level,
hdr_meta.cta_861_3->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);
cs.primaries = VideoColorSpace::PrimaryID::SMPTE170M;
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::SMPTE170M,
VideoColorSpace::TransferID::GAMMA22,
VideoColorSpace::MatrixID::SMPTE170M,
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::SMPTE170M,
VideoColorSpace::TransferID::GAMMA28,
VideoColorSpace::MatrixID::SMPTE170M,
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.
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.
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_FALSE(cs.IsValid());
}
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