blob: 77db95a11f8d439f0a1ef148300822506506b1af [file] [log] [blame]
/*
* Copyright (C) 2020-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "CMUtilities.h"
#if PLATFORM(COCOA)
#import "CAAudioStreamDescription.h"
#import "Logging.h"
#import "MediaSample.h"
#import "MediaUtilities.h"
#import "SharedBuffer.h"
#import "WebMAudioUtilitiesCocoa.h"
#import <CoreMedia/CMFormatDescription.h>
#import <pal/avfoundation/MediaTimeAVFoundation.h>
#import <wtf/Scope.h>
#import <wtf/cf/TypeCastsCF.h>
#import "CoreVideoSoftLink.h"
#import "VideoToolboxSoftLink.h"
#import <pal/cocoa/AVFoundationSoftLink.h>
#import <pal/cf/CoreMediaSoftLink.h>
#import <pal/cf/AudioToolboxSoftLink.h>
namespace WebCore {
#if ENABLE(OPUS) || ENABLE(VORBIS)
static RetainPtr<CMFormatDescriptionRef> createAudioFormatDescription(const AudioInfo& info)
{
AudioStreamBasicDescription asbd { };
asbd.mFormatID = info.codecName.value;
UInt32 size = sizeof(asbd);
auto error = PAL::AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, info.cookieData->size(), info.cookieData->data(), &size, &asbd);
if (error) {
RELEASE_LOG_ERROR(Media, "createAudioFormatDescription failed with error %d (%.4s)", error, (char *)&error);
return nullptr;
}
return createAudioFormatDescription(CAAudioStreamDescription(asbd), info.cookieData->size(), info.cookieData->data());
}
#endif
static CFStringRef convertToCMColorPrimaries(PlatformVideoColorPrimaries primaries)
{
switch (primaries) {
case PlatformVideoColorPrimaries::Bt709:
return kCVImageBufferColorPrimaries_ITU_R_709_2;
case PlatformVideoColorPrimaries::JedecP22Phosphors:
return kCVImageBufferColorPrimaries_EBU_3213;
case PlatformVideoColorPrimaries::Smpte170m:
case PlatformVideoColorPrimaries::Smpte240m:
return kCVImageBufferColorPrimaries_SMPTE_C;
case PlatformVideoColorPrimaries::SmpteRp431:
return PAL::kCMFormatDescriptionColorPrimaries_DCI_P3;
case PlatformVideoColorPrimaries::SmpteEg432:
return PAL::kCMFormatDescriptionColorPrimaries_P3_D65;
case PlatformVideoColorPrimaries::Bt2020:
return PAL::kCMFormatDescriptionColorPrimaries_ITU_R_2020;
default:
return nullptr;
}
}
static CFStringRef convertToCMTransferFunction(PlatformVideoTransferCharacteristics characteristics)
{
switch (characteristics) {
case PlatformVideoTransferCharacteristics::Bt709:
return kCVImageBufferTransferFunction_ITU_R_709_2;
case PlatformVideoTransferCharacteristics::Smpte240m:
return kCVImageBufferTransferFunction_SMPTE_240M_1995;
case PlatformVideoTransferCharacteristics::SmpteSt2084:
return PAL::kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ;
case PlatformVideoTransferCharacteristics::Bt2020_10bit:
case PlatformVideoTransferCharacteristics::Bt2020_12bit:
return PAL::kCMFormatDescriptionTransferFunction_ITU_R_2020;
case PlatformVideoTransferCharacteristics::SmpteSt4281:
return PAL::kCMFormatDescriptionTransferFunction_SMPTE_ST_428_1;
case PlatformVideoTransferCharacteristics::AribStdB67Hlg:
return PAL::kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG;
case PlatformVideoTransferCharacteristics::Iec6196621:
return PAL::canLoad_CoreMedia_kCMFormatDescriptionTransferFunction_sRGB() ? PAL::get_CoreMedia_kCMFormatDescriptionTransferFunction_sRGB() : nullptr;
case PlatformVideoTransferCharacteristics::Linear:
return PAL::kCMFormatDescriptionTransferFunction_Linear;
default:
return nullptr;
}
}
static CFStringRef convertToCMYCbCRMatrix(PlatformVideoMatrixCoefficients coefficients)
{
switch (coefficients) {
case PlatformVideoMatrixCoefficients::Bt2020NonconstantLuminance:
return PAL::kCMFormatDescriptionYCbCrMatrix_ITU_R_2020;
case PlatformVideoMatrixCoefficients::Bt470bg:
case PlatformVideoMatrixCoefficients::Smpte170m:
return kCVImageBufferYCbCrMatrix_ITU_R_601_4;
case PlatformVideoMatrixCoefficients::Bt709:
return kCVImageBufferYCbCrMatrix_ITU_R_709_2;
case PlatformVideoMatrixCoefficients::Smpte240m:
return kCVImageBufferYCbCrMatrix_SMPTE_240M_1995;
default:
return nullptr;
}
}
RetainPtr<CMFormatDescriptionRef> createFormatDescriptionFromTrackInfo(const TrackInfo& info)
{
ASSERT(info.isVideo() || info.isAudio());
if (info.isAudio()) {
auto& audioInfo = downcast<const AudioInfo>(info);
if (!audioInfo.cookieData || !audioInfo.cookieData->size())
return nullptr;
switch (audioInfo.codecName.value) {
#if ENABLE(OPUS)
case 'opus':
if (!isOpusDecoderAvailable())
return nullptr;
return createAudioFormatDescription(audioInfo);
#endif
#if ENABLE(VORBIS)
case 'vorb':
if (!isVorbisDecoderAvailable())
return nullptr;
return createAudioFormatDescription(audioInfo);
#endif
default:
return nullptr;
}
}
auto& videoInfo = downcast<const VideoInfo>(info);
auto data = videoInfo.atomData->createCFData();
ASSERT(videoInfo.codecName == 'vp09' || videoInfo.codecName == 'vp08');
CFTypeRef configurationKeys[] = { CFSTR("vpcC") };
CFTypeRef configurationValues[] = { data.get() };
auto configurationDict = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, configurationKeys, configurationValues, WTF_ARRAY_LENGTH(configurationKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
Vector<CFTypeRef> extensionsKeys { PAL::kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms };
Vector<CFTypeRef> extensionsValues = { configurationDict.get() };
if (videoInfo.colorSpace.fullRange && *videoInfo.colorSpace.fullRange) {
extensionsKeys.append(PAL::kCMFormatDescriptionExtension_FullRangeVideo);
extensionsValues.append(kCFBooleanTrue);
}
if (videoInfo.colorSpace.primaries) {
if (auto cmColorPrimaries = convertToCMColorPrimaries(*videoInfo.colorSpace.primaries)) {
extensionsKeys.append(kCVImageBufferColorPrimariesKey);
extensionsValues.append(cmColorPrimaries);
}
}
if (videoInfo.colorSpace.transfer) {
if (auto cmTransferFunction = convertToCMTransferFunction(*videoInfo.colorSpace.transfer)) {
extensionsKeys.append(kCVImageBufferTransferFunctionKey);
extensionsValues.append(cmTransferFunction);
}
}
if (videoInfo.colorSpace.matrix) {
if (auto cmMatrix = convertToCMYCbCRMatrix(*videoInfo.colorSpace.matrix)) {
extensionsKeys.append(kCVImageBufferYCbCrMatrixKey);
extensionsValues.append(cmMatrix);
}
}
auto extensions = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, extensionsKeys.data(), extensionsValues.data(), extensionsKeys.size(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CMVideoFormatDescriptionRef formatDescription = nullptr;
CMVideoCodecType codec = videoInfo.codecName == "vp09" ? kCMVideoCodecType_VP9 : 'vp08';
if (noErr != PAL::CMVideoFormatDescriptionCreate(kCFAllocatorDefault, codec, videoInfo.size.width(), videoInfo.size.height(), extensions.get(), &formatDescription))
return nullptr;
return adoptCF(formatDescription);
}
Expected<RetainPtr<CMSampleBufferRef>, CString> toCMSampleBuffer(MediaSamplesBlock&& samples, CMFormatDescriptionRef formatDescription)
{
if (!samples.info())
return makeUnexpected("No TrackInfo found");
auto scopeExit = makeScopeExit([&] {
samples.clear();
});
auto format = formatDescription ? retainPtr(formatDescription) : createFormatDescriptionFromTrackInfo(*samples.info());
if (!format)
return makeUnexpected("No CMFormatDescription available");
RetainPtr<CMBlockBufferRef> completeBlockBuffers;
if (samples.size() > 1) {
// Optimisation so that we allocate the entire CMBlockBuffer at once if we have more than one to return.
CMBlockBufferRef rawBlockBuffer = nullptr;
auto err = PAL::CMBlockBufferCreateEmpty(kCFAllocatorDefault, samples.size(), 0, &rawBlockBuffer);
if (err != kCMBlockBufferNoErr || !rawBlockBuffer)
return makeUnexpected("CMBlockBufferCreateEmpty failed");
completeBlockBuffers = adoptCF(rawBlockBuffer);
}
Vector<CMSampleTimingInfo> packetTimings;
packetTimings.reserveInitialCapacity(samples.size());
Vector<size_t> packetSizes;
packetSizes.reserveInitialCapacity(samples.size());
for (auto& sample : samples) {
if (!std::holds_alternative<Ref<const FragmentedSharedBuffer>>(sample.data))
return makeUnexpected("Invalid MediaSamplesBlock type");
auto& data = std::get<Ref<const FragmentedSharedBuffer>>(sample.data);
auto blockBuffer = data->createCMBlockBuffer();
if (!blockBuffer)
return makeUnexpected("Couldn't create CMBlockBuffer");
if (!completeBlockBuffers)
completeBlockBuffers = WTFMove(blockBuffer);
else {
auto err = PAL::CMBlockBufferAppendBufferReference(completeBlockBuffers.get(), blockBuffer.get(), 0, 0, 0);
if (err != kCMBlockBufferNoErr)
return makeUnexpected("CMBlockBufferAppendBufferReference failed");
}
packetTimings.append({ PAL::toCMTime(sample.duration), PAL::toCMTime(sample.presentationTime), PAL::toCMTime(sample.decodeTime) });
packetSizes.append(data->size());
}
CMSampleBufferRef rawSampleBuffer = nullptr;
if (auto err = PAL::CMSampleBufferCreateReady(kCFAllocatorDefault, completeBlockBuffers.get(), format.get(), packetSizes.size(), packetTimings.size(), packetTimings.data(), packetSizes.size(), packetSizes.data(), &rawSampleBuffer))
return makeUnexpected("CMSampleBufferCreateReady failed: OOM");
if (samples.isVideo() && samples.size()) {
auto attachmentsArray = PAL::CMSampleBufferGetSampleAttachmentsArray(rawSampleBuffer, true);
ASSERT(attachmentsArray);
if (!attachmentsArray)
return makeUnexpected("No sample attachment found");
ASSERT(size_t(CFArrayGetCount(attachmentsArray)) == samples.size());
for (CFIndex i = 0, count = CFArrayGetCount(attachmentsArray); i < count; ++i) {
CFMutableDictionaryRef attachments = checked_cf_cast<CFMutableDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, i));
if (!(samples[i].flags & MediaSample::SampleFlags::IsSync))
CFDictionarySetValue(attachments, PAL::kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
}
}
return adoptCF(rawSampleBuffer);
}
} // namespace WebCore
#endif // PLATFORM(COCOA)