blob: d75c63c06f78ad245761ce844fb4415dde6f85e5 [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/base/mac/color_space_util_mac.h"
#include <simd/simd.h>
#include <vector>
#include "base/mac/foundation_util.h"
#include "base/no_destructor.h"
#include "third_party/skia/include/third_party/skcms/skcms.h"
namespace media {
namespace {
// Read the value for the key in |key| to CFString and convert it to IdType.
// Use the list of pairs in |cfstr_id_pairs| to do the conversion (by doing a
// linear lookup).
template <typename IdType, typename StringIdPair>
bool GetImageBufferProperty(CFTypeRef value_untyped,
const std::vector<StringIdPair>& cfstr_id_pairs,
IdType* value_as_id) {
CFStringRef value_as_string = base::mac::CFCast<CFStringRef>(value_untyped);
if (!value_as_string)
return false;
for (const auto& p : cfstr_id_pairs) {
if (p.cfstr_cm)
DCHECK(!CFStringCompare(p.cfstr_cv, p.cfstr_cm, 0));
if (!CFStringCompare(value_as_string, p.cfstr_cv, 0)) {
*value_as_id = p.id;
return true;
}
}
return false;
}
gfx::ColorSpace::PrimaryID GetCoreVideoPrimary(CFTypeRef primaries_untyped) {
struct CVImagePrimary {
const CFStringRef cfstr_cv;
const CFStringRef cfstr_cm;
const gfx::ColorSpace::PrimaryID id;
};
static const base::NoDestructor<std::vector<CVImagePrimary>>
kSupportedPrimaries([] {
std::vector<CVImagePrimary> supported_primaries;
supported_primaries.push_back(
{kCVImageBufferColorPrimaries_ITU_R_709_2,
kCMFormatDescriptionColorPrimaries_ITU_R_709_2,
gfx::ColorSpace::PrimaryID::BT709});
supported_primaries.push_back(
{kCVImageBufferColorPrimaries_EBU_3213,
kCMFormatDescriptionColorPrimaries_EBU_3213,
gfx::ColorSpace::PrimaryID::BT470BG});
supported_primaries.push_back(
{kCVImageBufferColorPrimaries_SMPTE_C,
kCMFormatDescriptionColorPrimaries_SMPTE_C,
gfx::ColorSpace::PrimaryID::SMPTE240M});
supported_primaries.push_back(
{kCVImageBufferColorPrimaries_ITU_R_2020,
kCMFormatDescriptionColorPrimaries_ITU_R_2020,
gfx::ColorSpace::PrimaryID::BT2020});
return supported_primaries;
}());
// The named primaries. Default to BT709.
auto primary_id = gfx::ColorSpace::PrimaryID::BT709;
if (!GetImageBufferProperty(primaries_untyped, *kSupportedPrimaries,
&primary_id)) {
DLOG(ERROR) << "Failed to find CVImageBufferRef primaries.";
}
return primary_id;
}
gfx::ColorSpace::TransferID GetCoreVideoTransferFn(CFTypeRef transfer_untyped,
CFTypeRef gamma_untyped,
double* gamma) {
struct CVImageTransferFn {
const CFStringRef cfstr_cv;
const CFStringRef cfstr_cm;
const gfx::ColorSpace::TransferID id;
};
static const base::NoDestructor<std::vector<CVImageTransferFn>>
kSupportedTransferFuncs([] {
std::vector<CVImageTransferFn> supported_transfer_funcs;
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_ITU_R_709_2,
kCMFormatDescriptionTransferFunction_ITU_R_709_2,
gfx::ColorSpace::TransferID::BT709_APPLE});
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_SMPTE_240M_1995,
kCMFormatDescriptionTransferFunction_SMPTE_240M_1995,
gfx::ColorSpace::TransferID::SMPTE240M});
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_UseGamma,
kCMFormatDescriptionTransferFunction_UseGamma,
gfx::ColorSpace::TransferID::CUSTOM});
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_ITU_R_2020,
kCMFormatDescriptionTransferFunction_ITU_R_2020,
gfx::ColorSpace::TransferID::BT2020_10});
if (@available(macos 10.12, *)) {
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_SMPTE_ST_428_1,
kCMFormatDescriptionTransferFunction_SMPTE_ST_428_1,
gfx::ColorSpace::TransferID::SMPTEST428_1});
}
if (@available(macos 10.13, *)) {
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ,
kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ,
gfx::ColorSpace::TransferID::SMPTEST2084});
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_ITU_R_2100_HLG,
kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG,
gfx::ColorSpace::TransferID::ARIB_STD_B67});
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_sRGB, nullptr,
gfx::ColorSpace::TransferID::IEC61966_2_1});
}
if (@available(macos 10.14, *)) {
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_Linear,
kCMFormatDescriptionTransferFunction_Linear,
gfx::ColorSpace::TransferID::LINEAR});
}
if (@available(macos 10.15, *)) {
supported_transfer_funcs.push_back(
{kCVImageBufferTransferFunction_sRGB,
kCMFormatDescriptionTransferFunction_sRGB,
gfx::ColorSpace::TransferID::IEC61966_2_1});
}
return supported_transfer_funcs;
}());
// The named transfer function.
auto transfer_id = gfx::ColorSpace::TransferID::BT709;
if (!GetImageBufferProperty(transfer_untyped, *kSupportedTransferFuncs,
&transfer_id)) {
DLOG(ERROR) << "Failed to find CVImageBufferRef transfer.";
}
if (transfer_id != gfx::ColorSpace::TransferID::CUSTOM)
return transfer_id;
// If we fail to retrieve the gamma parameter, fall back to BT709.
constexpr auto kDefaultTransferFn = gfx::ColorSpace::TransferID::BT709;
CFNumberRef gamma_number = base::mac::CFCast<CFNumberRef>(gamma_untyped);
if (!gamma_number) {
DLOG(ERROR) << "Failed to get gamma level.";
return kDefaultTransferFn;
}
// CGFloat is a double on 64-bit systems.
CGFloat gamma_double = 0;
if (!CFNumberGetValue(gamma_number, kCFNumberCGFloatType, &gamma_double)) {
DLOG(ERROR) << "Failed to get CVImageBufferRef gamma level as float.";
return kDefaultTransferFn;
}
if (gamma_double == 2.2)
return gfx::ColorSpace::TransferID::GAMMA22;
if (gamma_double == 2.8)
return gfx::ColorSpace::TransferID::GAMMA28;
*gamma = gamma_double;
return transfer_id;
}
gfx::ColorSpace::MatrixID GetCoreVideoMatrix(CFTypeRef matrix_untyped) {
struct CVImageMatrix {
const CFStringRef cfstr_cv;
const CFStringRef cfstr_cm;
gfx::ColorSpace::MatrixID id;
};
static const base::NoDestructor<std::vector<CVImageMatrix>>
kSupportedMatrices([] {
std::vector<CVImageMatrix> supported_matrices;
supported_matrices.push_back(
{kCVImageBufferYCbCrMatrix_ITU_R_709_2,
kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2,
gfx::ColorSpace::MatrixID::BT709});
supported_matrices.push_back(
{kCVImageBufferYCbCrMatrix_ITU_R_601_4,
kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4,
gfx::ColorSpace::MatrixID::SMPTE170M});
supported_matrices.push_back(
{kCVImageBufferYCbCrMatrix_SMPTE_240M_1995,
kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995,
gfx::ColorSpace::MatrixID::SMPTE240M});
supported_matrices.push_back(
{kCVImageBufferYCbCrMatrix_ITU_R_2020,
kCMFormatDescriptionYCbCrMatrix_ITU_R_2020,
gfx::ColorSpace::MatrixID::BT2020_NCL});
return supported_matrices;
}());
auto matrix_id = gfx::ColorSpace::MatrixID::INVALID;
if (!GetImageBufferProperty(matrix_untyped, *kSupportedMatrices,
&matrix_id)) {
DLOG(ERROR) << "Failed to find CVImageBufferRef YUV matrix.";
}
return matrix_id;
}
gfx::ColorSpace GetCoreVideoColorSpaceInternal(CFTypeRef primaries_untyped,
CFTypeRef transfer_untyped,
CFTypeRef gamma_untyped,
CFTypeRef matrix_untyped) {
double gamma;
auto primary_id = GetCoreVideoPrimary(primaries_untyped);
auto matrix_id = GetCoreVideoMatrix(matrix_untyped);
auto transfer_id =
GetCoreVideoTransferFn(transfer_untyped, gamma_untyped, &gamma);
// Use a matrix id that is coherent with a primary id. Useful when we fail to
// parse the matrix. Previously it was always defaulting to MatrixID::BT709
// See http://crbug.com/788236.
if (matrix_id == gfx::ColorSpace::MatrixID::INVALID) {
if (primary_id == gfx::ColorSpace::PrimaryID::BT470BG)
matrix_id = gfx::ColorSpace::MatrixID::BT470BG;
else
matrix_id = gfx::ColorSpace::MatrixID::BT709;
}
// It is specified to the decoder to use luma=[16,235] chroma=[16,240] via
// the kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange.
//
// TODO(crbug.com/1103432): We'll probably need support for more than limited
// range content if we want this to be used for more than video sites.
auto range_id = gfx::ColorSpace::RangeID::LIMITED;
if (transfer_id == gfx::ColorSpace::TransferID::CUSTOM) {
// Transfer functions can also be specified as a gamma value.
skcms_TransferFunction custom_tr_fn = {2.2f, 1, 0, 1, 0, 0, 0};
if (transfer_id == gfx::ColorSpace::TransferID::CUSTOM)
custom_tr_fn.g = gamma;
return gfx::ColorSpace(primary_id, gfx::ColorSpace::TransferID::CUSTOM,
matrix_id, range_id, nullptr, &custom_tr_fn);
}
return gfx::ColorSpace(primary_id, transfer_id, matrix_id, range_id);
}
} // anonymous namespace
gfx::ColorSpace GetImageBufferColorSpace(CVImageBufferRef image_buffer) {
return GetCoreVideoColorSpaceInternal(
CVBufferGetAttachment(image_buffer, kCVImageBufferColorPrimariesKey,
nullptr),
CVBufferGetAttachment(image_buffer, kCVImageBufferTransferFunctionKey,
nullptr),
CVBufferGetAttachment(image_buffer, kCVImageBufferGammaLevelKey, nullptr),
CVBufferGetAttachment(image_buffer, kCVImageBufferYCbCrMatrixKey,
nullptr));
}
gfx::ColorSpace GetFormatDescriptionColorSpace(
CMFormatDescriptionRef format_description) {
return GetCoreVideoColorSpaceInternal(
CMFormatDescriptionGetExtension(
format_description, kCMFormatDescriptionExtension_ColorPrimaries),
CMFormatDescriptionGetExtension(
format_description, kCMFormatDescriptionExtension_TransferFunction),
CMFormatDescriptionGetExtension(format_description,
kCMFormatDescriptionExtension_GammaLevel),
CMFormatDescriptionGetExtension(
format_description, kCMFormatDescriptionExtension_YCbCrMatrix));
}
CFDataRef GenerateContentLightLevelInfo(const gfx::HDRMetadata& hdr_metadata) {
// This is a SMPTEST2086 Content Light Level Information box.
struct ContentLightLevelInfoSEI {
uint16_t max_content_light_level;
uint16_t max_frame_average_light_level;
} __attribute__((packed, aligned(2)));
static_assert(sizeof(ContentLightLevelInfoSEI) == 4, "Must be 4 bytes");
// Values are stored in big-endian...
ContentLightLevelInfoSEI sei;
sei.max_content_light_level =
__builtin_bswap16(hdr_metadata.max_content_light_level);
sei.max_frame_average_light_level =
__builtin_bswap16(hdr_metadata.max_frame_average_light_level);
NSData* nsdata_sei = [NSData dataWithBytes:&sei length:4];
return base::mac::NSToCFCast(nsdata_sei);
}
CFDataRef GenerateMasteringDisplayColorVolume(
const gfx::HDRMetadata& hdr_metadata) {
// This is a SMPTEST2086 Mastering Display Color Volume box.
struct MasteringDisplayColorVolumeSEI {
vector_ushort2 primaries[3]; // GBR
vector_ushort2 white_point;
uint32_t luminance_max;
uint32_t luminance_min;
} __attribute__((packed, aligned(4)));
static_assert(sizeof(MasteringDisplayColorVolumeSEI) == 24,
"Must be 24 bytes");
// Make a copy which we can manipulate.
auto md = hdr_metadata.color_volume_metadata;
constexpr float kColorCoordinateUpperBound = 50000.0f;
md.primary_r.Scale(kColorCoordinateUpperBound);
md.primary_g.Scale(kColorCoordinateUpperBound);
md.primary_b.Scale(kColorCoordinateUpperBound);
md.white_point.Scale(kColorCoordinateUpperBound);
constexpr float kUnitOfMasteringLuminance = 10000.0f;
md.luminance_max *= kUnitOfMasteringLuminance;
md.luminance_min *= kUnitOfMasteringLuminance;
// Values are stored in big-endian...
MasteringDisplayColorVolumeSEI sei;
sei.primaries[0].x = __builtin_bswap16(md.primary_g.x() + 0.5f);
sei.primaries[0].y = __builtin_bswap16(md.primary_g.y() + 0.5f);
sei.primaries[1].x = __builtin_bswap16(md.primary_b.x() + 0.5f);
sei.primaries[1].y = __builtin_bswap16(md.primary_b.y() + 0.5f);
sei.primaries[2].x = __builtin_bswap16(md.primary_r.x() + 0.5f);
sei.primaries[2].y = __builtin_bswap16(md.primary_r.y() + 0.5f);
sei.white_point.x = __builtin_bswap16(md.white_point.x() + 0.5f);
sei.white_point.y = __builtin_bswap16(md.white_point.y() + 0.5f);
sei.luminance_max = __builtin_bswap32(md.luminance_max + 0.5f);
sei.luminance_min = __builtin_bswap32(md.luminance_min + 0.5f);
NSData* nsdata_sei = [NSData dataWithBytes:&sei length:24];
return base::mac::NSToCFCast(nsdata_sei);
}
} // namespace media