/* * Copyright (C) 2016-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. ``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 * 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 "MediaSampleAVFObjC.h" #import "CVUtilities.h" #import "PixelBuffer.h" #import "PixelBufferConformerCV.h" #import "ProcessIdentity.h" #import "VideoFrameCV.h" #import #import #import #import #import "CoreVideoSoftLink.h" #import // Equivalent to WTF_DECLARE_CF_TYPE_TRAIT(CMSampleBuffer); // Needed due to requirement of specifying the PAL namespace. template <> struct WTF::CFTypeTrait { static inline CFTypeID typeID(void) { return PAL::CMSampleBufferGetTypeID(); } }; namespace WebCore { MediaSampleAVFObjC::MediaSampleAVFObjC(RetainPtr&& sample) : m_sample(WTFMove(sample)) { } MediaSampleAVFObjC::MediaSampleAVFObjC(CMSampleBufferRef sample) : m_sample(sample) { } MediaSampleAVFObjC::MediaSampleAVFObjC(CMSampleBufferRef sample, AtomString trackID) : m_sample(sample) , m_id(trackID) { } MediaSampleAVFObjC::MediaSampleAVFObjC(CMSampleBufferRef sample, uint64_t trackID) : m_sample(sample) , m_id(AtomString::number(trackID)) { } MediaSampleAVFObjC::~MediaSampleAVFObjC() = default; MediaTime MediaSampleAVFObjC::presentationTime() const { auto timeStamp = PAL::CMSampleBufferGetOutputPresentationTimeStamp(m_sample.get()); if (CMTIME_IS_INVALID(timeStamp)) timeStamp = PAL::CMSampleBufferGetPresentationTimeStamp(m_sample.get()); return PAL::toMediaTime(timeStamp); } MediaTime MediaSampleAVFObjC::decodeTime() const { auto timeStamp = PAL::CMSampleBufferGetDecodeTimeStamp(m_sample.get()); if (CMTIME_IS_INVALID(timeStamp)) return presentationTime(); return PAL::toMediaTime(timeStamp); } MediaTime MediaSampleAVFObjC::duration() const { auto duration = PAL::CMSampleBufferGetOutputDuration(m_sample.get()); if (CMTIME_IS_INVALID(duration)) duration = PAL::CMSampleBufferGetDuration(m_sample.get()); return PAL::toMediaTime(duration); } size_t MediaSampleAVFObjC::sizeInBytes() const { return PAL::CMSampleBufferGetTotalSampleSize(m_sample.get()); } PlatformSample MediaSampleAVFObjC::platformSample() const { PlatformSample sample = { PlatformSample::CMSampleBufferType, { .cmSampleBuffer = m_sample.get() } }; return sample; } static bool isCMSampleBufferAttachmentRandomAccess(CFDictionaryRef attachmentDict) { return !CFDictionaryContainsKey(attachmentDict, PAL::kCMSampleAttachmentKey_NotSync); } static bool doesCMSampleBufferHaveSyncInfo(CMSampleBufferRef sample) { return PAL::CMSampleBufferGetSampleAttachmentsArray(sample, false); } static bool isCMSampleBufferRandomAccess(CMSampleBufferRef sample) { CFArrayRef attachments = PAL::CMSampleBufferGetSampleAttachmentsArray(sample, false); if (!attachments) return true; for (CFIndex i = 0, count = CFArrayGetCount(attachments); i < count; ++i) { if (!isCMSampleBufferAttachmentRandomAccess(checked_cf_cast(CFArrayGetValueAtIndex(attachments, i)))) return false; } return true; } static bool isCMSampleBufferAttachmentNonDisplaying(CFDictionaryRef attachmentDict) { return CFDictionaryContainsKey(attachmentDict, PAL::kCMSampleAttachmentKey_DoNotDisplay); } static bool isCMSampleBufferNonDisplaying(CMSampleBufferRef sample) { CFArrayRef attachments = PAL::CMSampleBufferGetSampleAttachmentsArray(sample, false); if (!attachments) return false; for (CFIndex i = 0; i < CFArrayGetCount(attachments); ++i) { if (isCMSampleBufferAttachmentNonDisplaying(checked_cf_cast(CFArrayGetValueAtIndex(attachments, i)))) return true; } return false; } MediaSample::SampleFlags MediaSampleAVFObjC::flags() const { int returnValue = MediaSample::None; if (doesCMSampleBufferHaveSyncInfo(m_sample.get())) returnValue |= MediaSample::HasSyncInfo; if (isCMSampleBufferRandomAccess(m_sample.get())) returnValue |= MediaSample::IsSync; if (isCMSampleBufferNonDisplaying(m_sample.get())) returnValue |= MediaSample::IsNonDisplaying; return SampleFlags(returnValue); } FloatSize MediaSampleAVFObjC::presentationSize() const { CMFormatDescriptionRef formatDescription = PAL::CMSampleBufferGetFormatDescription(m_sample.get()); if (PAL::CMFormatDescriptionGetMediaType(formatDescription) != kCMMediaType_Video) return FloatSize(); return FloatSize(PAL::CMVideoFormatDescriptionGetPresentationDimensions(formatDescription, true, true)); } void MediaSampleAVFObjC::offsetTimestampsBy(const MediaTime& offset) { CMItemCount itemCount = 0; if (noErr != PAL::CMSampleBufferGetSampleTimingInfoArray(m_sample.get(), 0, nullptr, &itemCount)) return; Vector timingInfoArray; timingInfoArray.grow(itemCount); if (noErr != PAL::CMSampleBufferGetSampleTimingInfoArray(m_sample.get(), itemCount, timingInfoArray.data(), nullptr)) return; for (auto& timing : timingInfoArray) { timing.presentationTimeStamp = PAL::toCMTime(PAL::toMediaTime(timing.presentationTimeStamp) + offset); timing.decodeTimeStamp = PAL::toCMTime(PAL::toMediaTime(timing.decodeTimeStamp) + offset); } CMSampleBufferRef newSample; if (noErr != PAL::CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, m_sample.get(), itemCount, timingInfoArray.data(), &newSample)) return; m_sample = adoptCF(newSample); } void MediaSampleAVFObjC::setTimestamps(const WTF::MediaTime &presentationTimestamp, const WTF::MediaTime &decodeTimestamp) { CMItemCount itemCount = 0; if (noErr != PAL::CMSampleBufferGetSampleTimingInfoArray(m_sample.get(), 0, nullptr, &itemCount)) return; Vector timingInfoArray; timingInfoArray.grow(itemCount); if (noErr != PAL::CMSampleBufferGetSampleTimingInfoArray(m_sample.get(), itemCount, timingInfoArray.data(), nullptr)) return; for (auto& timing : timingInfoArray) { timing.presentationTimeStamp = PAL::toCMTime(presentationTimestamp); timing.decodeTimeStamp = PAL::toCMTime(decodeTimestamp); } CMSampleBufferRef newSample; if (noErr != PAL::CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, m_sample.get(), itemCount, timingInfoArray.data(), &newSample)) return; m_sample = adoptCF(newSample); } bool MediaSampleAVFObjC::isDivisable() const { if (PAL::CMSampleBufferGetNumSamples(m_sample.get()) == 1) return false; if (PAL::CMSampleBufferGetSampleSizeArray(m_sample.get(), 0, nullptr, nullptr) == kCMSampleBufferError_BufferHasNoSampleSizes) return false; return true; } std::pair, RefPtr> MediaSampleAVFObjC::divide(const MediaTime& presentationTime, UseEndTime useEndTime) { if (!isDivisable()) return { nullptr, nullptr }; CFIndex samplesBeforePresentationTime = 0; PAL::CMSampleBufferCallBlockForEachSample(m_sample.get(), [&] (CMSampleBufferRef sampleBuffer, CMItemCount) -> OSStatus { auto timeStamp = PAL::CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer); if (CMTIME_IS_INVALID(timeStamp)) timeStamp = PAL::CMSampleBufferGetPresentationTimeStamp(sampleBuffer); if (useEndTime == UseEndTime::Use) { auto duration = PAL::CMSampleBufferGetOutputDuration(sampleBuffer); if (CMTIME_IS_INVALID(duration)) duration = PAL::CMSampleBufferGetDuration(sampleBuffer); if (PAL::toMediaTime(PAL::CMTimeAdd(timeStamp, duration)) > presentationTime) return 1; } else if (PAL::toMediaTime(timeStamp) >= presentationTime) return 1; ++samplesBeforePresentationTime; return noErr; }); if (!samplesBeforePresentationTime) return { nullptr, this }; CMItemCount sampleCount = PAL::CMSampleBufferGetNumSamples(m_sample.get()); if (samplesBeforePresentationTime >= sampleCount) return { this, nullptr }; CMSampleBufferRef rawSampleBefore = nullptr; CFRange rangeBefore = CFRangeMake(0, samplesBeforePresentationTime); if (PAL::CMSampleBufferCopySampleBufferForRange(kCFAllocatorDefault, m_sample.get(), rangeBefore, &rawSampleBefore) != noErr) return { nullptr, nullptr }; RetainPtr sampleBefore = adoptCF(rawSampleBefore); CMSampleBufferRef rawSampleAfter = nullptr; CFRange rangeAfter = CFRangeMake(samplesBeforePresentationTime, sampleCount - samplesBeforePresentationTime); if (PAL::CMSampleBufferCopySampleBufferForRange(kCFAllocatorDefault, m_sample.get(), rangeAfter, &rawSampleAfter) != noErr) return { nullptr, nullptr }; RetainPtr sampleAfter = adoptCF(rawSampleAfter); return { MediaSampleAVFObjC::create(sampleBefore.get(), m_id), MediaSampleAVFObjC::create(sampleAfter.get(), m_id) }; } Ref MediaSampleAVFObjC::createNonDisplayingCopy() const { CMSampleBufferRef newSampleBuffer = 0; PAL::CMSampleBufferCreateCopy(kCFAllocatorDefault, m_sample.get(), &newSampleBuffer); ASSERT(newSampleBuffer); CMFormatDescriptionRef formatDescription = PAL::CMSampleBufferGetFormatDescription(m_sample.get()); bool isAudio = PAL::CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio; const CFStringRef attachmentKey = isAudio ? PAL::kCMSampleBufferAttachmentKey_TrimDurationAtStart : PAL::kCMSampleAttachmentKey_DoNotDisplay; CFArrayRef attachmentsArray = PAL::CMSampleBufferGetSampleAttachmentsArray(newSampleBuffer, true); for (CFIndex i = 0; i < CFArrayGetCount(attachmentsArray); ++i) { CFMutableDictionaryRef attachments = checked_cf_cast(CFArrayGetValueAtIndex(attachmentsArray, i)); CFDictionarySetValue(attachments, attachmentKey, kCFBooleanTrue); } return MediaSampleAVFObjC::create(adoptCF(newSampleBuffer).get(), m_id); } bool MediaSampleAVFObjC::isHomogeneous() const { CFArrayRef attachmentsArray = PAL::CMSampleBufferGetSampleAttachmentsArray(m_sample.get(), true); if (!attachmentsArray) return true; auto count = CFArrayGetCount(attachmentsArray); if (count <= 1) return true; CFDictionaryRef firstAttachment = checked_cf_cast(CFArrayGetValueAtIndex(attachmentsArray, 0)); bool isSync = isCMSampleBufferAttachmentRandomAccess(firstAttachment); bool isNonDisplaying = isCMSampleBufferAttachmentNonDisplaying(firstAttachment); for (CFIndex i = 1; i < count; ++i) { auto attachmentDict = checked_cf_cast(CFArrayGetValueAtIndex(attachmentsArray, i)); if (isSync != isCMSampleBufferAttachmentRandomAccess(attachmentDict)) return false; if (isNonDisplaying != isCMSampleBufferAttachmentNonDisplaying(attachmentDict)) return false; }; return true; } Vector> MediaSampleAVFObjC::divideIntoHomogeneousSamples() { using SampleVector = Vector>; CFArrayRef attachmentsArray = PAL::CMSampleBufferGetSampleAttachmentsArray(m_sample.get(), true); if (!attachmentsArray) return SampleVector::from(Ref { *this }); auto count = CFArrayGetCount(attachmentsArray); if (count <= 1) return SampleVector::from(Ref { *this }); CFDictionaryRef firstAttachment = checked_cf_cast(CFArrayGetValueAtIndex(attachmentsArray, 0)); bool isSync = isCMSampleBufferAttachmentRandomAccess(firstAttachment); bool isNonDisplaying = isCMSampleBufferAttachmentNonDisplaying(firstAttachment); Vector ranges; CFIndex currentRangeStart = 0; CFIndex currentRangeLength = 1; for (CFIndex i = 1; i < count; ++i, ++currentRangeLength) { CFDictionaryRef attachmentDict = checked_cf_cast(CFArrayGetValueAtIndex(attachmentsArray, i)); if (isSync == isCMSampleBufferAttachmentRandomAccess(attachmentDict) && isNonDisplaying == isCMSampleBufferAttachmentNonDisplaying(attachmentDict)) continue; ranges.append(CFRangeMake(currentRangeStart, currentRangeLength)); currentRangeStart = i; currentRangeLength = 0; } ranges.append(CFRangeMake(currentRangeStart, currentRangeLength)); if (ranges.size() == 1) return SampleVector::from(Ref { *this }); SampleVector samples; samples.reserveInitialCapacity(ranges.size()); for (auto& range : ranges) { CMSampleBufferRef rawSample = nullptr; if (PAL::CMSampleBufferCopySampleBufferForRange(kCFAllocatorDefault, m_sample.get(), range, &rawSample) != noErr || !rawSample) return { }; samples.uncheckedAppend(MediaSampleAVFObjC::create(adoptCF(rawSample).get(), m_id)); } return samples; } }