/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AppleVTDecoder.h" #include #include #include #include "AppleDecoderModule.h" #include "AppleUtils.h" #include "CallbackThreadRegistry.h" #include "H264.h" #include "MP4Decoder.h" #include "MacIOSurfaceImage.h" #include "MediaData.h" #include "VPXDecoder.h" #include "VideoUtils.h" #include "gfxMacUtils.h" #include "gfxPlatform.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Logging.h" #include "mozilla/TaskQueue.h" #include "mozilla/gfx/gfxVars.h" #include "nsThreadUtils.h" #define LOG(...) DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__) #define LOGEX(_this, ...) \ DDMOZ_LOGEX(_this, sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__) namespace mozilla { using namespace layers; AppleVTDecoder::AppleVTDecoder(const VideoInfo& aConfig, layers::ImageContainer* aImageContainer, CreateDecoderParams::OptionSet aOptions, layers::KnowsCompositor* aKnowsCompositor, Maybe aTrackingId) : mExtraData(aConfig.mExtraData), mPictureWidth(aConfig.mImage.width), mPictureHeight(aConfig.mImage.height), mDisplayWidth(aConfig.mDisplay.width), mDisplayHeight(aConfig.mDisplay.height), mColorSpace(aConfig.mColorSpace ? *aConfig.mColorSpace : DefaultColorSpace({mPictureWidth, mPictureHeight})), mColorPrimaries(aConfig.mColorPrimaries ? *aConfig.mColorPrimaries : gfx::ColorSpace2::BT709), mTransferFunction(aConfig.mTransferFunction ? *aConfig.mTransferFunction : gfx::TransferFunction::BT709), mColorRange(aConfig.mColorRange), mColorDepth(aConfig.mColorDepth), mStreamType(MP4Decoder::IsH264(aConfig.mMimeType) ? StreamType::H264 : VPXDecoder::IsVP9(aConfig.mMimeType) ? StreamType::VP9 : StreamType::Unknown), mTaskQueue(TaskQueue::Create( GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), "AppleVTDecoder")), mMaxRefFrames( mStreamType != StreamType::H264 || aOptions.contains(CreateDecoderParams::Option::LowLatency) ? 0 : H264::ComputeMaxRefFrames(aConfig.mExtraData)), mImageContainer(aImageContainer), mKnowsCompositor(aKnowsCompositor) #ifdef MOZ_WIDGET_UIKIT , mUseSoftwareImages(true) #else , mUseSoftwareImages(aKnowsCompositor && aKnowsCompositor->GetWebRenderCompositorType() == layers::WebRenderCompositor::SOFTWARE) #endif , mTrackingId(aTrackingId), mIsFlushing(false), mCallbackThreadId(), mMonitor("AppleVTDecoder"), mPromise(&mMonitor), // To ensure our PromiseHolder is only ever accessed // with the monitor held. mFormat(nullptr), mSession(nullptr), mIsHardwareAccelerated(false) { MOZ_COUNT_CTOR(AppleVTDecoder); MOZ_ASSERT(mStreamType != StreamType::Unknown); // TODO: Verify aConfig.mime_type. LOG("Creating AppleVTDecoder for %dx%d %s video", mDisplayWidth, mDisplayHeight, mStreamType == StreamType::H264 ? "H.264" : "VP9"); } AppleVTDecoder::~AppleVTDecoder() { MOZ_COUNT_DTOR(AppleVTDecoder); } RefPtr AppleVTDecoder::Init() { MediaResult rv = InitializeSession(); if (NS_SUCCEEDED(rv)) { return InitPromise::CreateAndResolve(TrackType::kVideoTrack, __func__); } return InitPromise::CreateAndReject(rv, __func__); } RefPtr AppleVTDecoder::Decode( MediaRawData* aSample) { LOG("mp4 input sample %p pts %lld duration %lld us%s %zu bytes", aSample, aSample->mTime.ToMicroseconds(), aSample->mDuration.ToMicroseconds(), aSample->mKeyframe ? " keyframe" : "", aSample->Size()); RefPtr self = this; RefPtr sample = aSample; return InvokeAsync(mTaskQueue, __func__, [self, this, sample] { RefPtr p; { MonitorAutoLock mon(mMonitor); p = mPromise.Ensure(__func__); } ProcessDecode(sample); return p; }); } RefPtr AppleVTDecoder::Flush() { mIsFlushing = true; return InvokeAsync(mTaskQueue, this, __func__, &AppleVTDecoder::ProcessFlush); } RefPtr AppleVTDecoder::Drain() { return InvokeAsync(mTaskQueue, this, __func__, &AppleVTDecoder::ProcessDrain); } RefPtr AppleVTDecoder::Shutdown() { RefPtr self = this; return InvokeAsync(mTaskQueue, __func__, [self]() { self->ProcessShutdown(); return self->mTaskQueue->BeginShutdown(); }); } // Helper to fill in a timestamp structure. static CMSampleTimingInfo TimingInfoFromSample(MediaRawData* aSample) { CMSampleTimingInfo timestamp; timestamp.duration = CMTimeMake(aSample->mDuration.ToMicroseconds(), USECS_PER_S); timestamp.presentationTimeStamp = CMTimeMake(aSample->mTime.ToMicroseconds(), USECS_PER_S); timestamp.decodeTimeStamp = CMTimeMake(aSample->mTimecode.ToMicroseconds(), USECS_PER_S); return timestamp; } void AppleVTDecoder::ProcessDecode(MediaRawData* aSample) { AssertOnTaskQueue(); PROCESS_DECODE_LOG(aSample); if (mIsFlushing) { MonitorAutoLock mon(mMonitor); mPromise.Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); return; } mTrackingId.apply([&](const auto& aId) { MediaInfoFlag flag = MediaInfoFlag::None; flag |= (aSample->mKeyframe ? MediaInfoFlag::KeyFrame : MediaInfoFlag::NonKeyFrame); flag |= (mIsHardwareAccelerated ? MediaInfoFlag::HardwareDecoding : MediaInfoFlag::SoftwareDecoding); switch (mStreamType) { case StreamType::H264: flag |= MediaInfoFlag::VIDEO_H264; break; case StreamType::VP9: flag |= MediaInfoFlag::VIDEO_VP9; break; default: break; } mPerformanceRecorder.Start(aSample->mTimecode.ToMicroseconds(), "AppleVTDecoder"_ns, aId, flag); }); AutoCFRelease block = nullptr; AutoCFRelease sample = nullptr; VTDecodeInfoFlags infoFlags; OSStatus rv; // FIXME: This copies the sample data. I think we can provide // a custom block source which reuses the aSample buffer. // But note that there may be a problem keeping the samples // alive over multiple frames. rv = CMBlockBufferCreateWithMemoryBlock( kCFAllocatorDefault, // Struct allocator. const_cast(aSample->Data()), aSample->Size(), kCFAllocatorNull, // Block allocator. NULL, // Block source. 0, // Data offset. aSample->Size(), false, block.receive()); if (rv != noErr) { NS_ERROR("Couldn't create CMBlockBuffer"); MonitorAutoLock mon(mMonitor); mPromise.Reject( MediaResult(NS_ERROR_OUT_OF_MEMORY, RESULT_DETAIL("CMBlockBufferCreateWithMemoryBlock:%x", rv)), __func__); return; } CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample); rv = CMSampleBufferCreate(kCFAllocatorDefault, block, true, 0, 0, mFormat, 1, 1, ×tamp, 0, NULL, sample.receive()); if (rv != noErr) { NS_ERROR("Couldn't create CMSampleBuffer"); MonitorAutoLock mon(mMonitor); mPromise.Reject(MediaResult(NS_ERROR_OUT_OF_MEMORY, RESULT_DETAIL("CMSampleBufferCreate:%x", rv)), __func__); return; } VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression; rv = VTDecompressionSessionDecodeFrame( mSession, sample, decodeFlags, CreateAppleFrameRef(aSample), &infoFlags); if (infoFlags & kVTDecodeInfo_FrameDropped) { MonitorAutoLock mon(mMonitor); // Smile and nod NS_WARNING("Decoder synchronously dropped frame"); MaybeResolveBufferedFrames(); return; } if (rv != noErr) { LOG("AppleVTDecoder: Error %d VTDecompressionSessionDecodeFrame", rv); NS_WARNING("Couldn't pass frame to decoder"); // It appears that even when VTDecompressionSessionDecodeFrame returned a // failure. Decoding sometimes actually get processed. MonitorAutoLock mon(mMonitor); mPromise.RejectIfExists( MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, RESULT_DETAIL("VTDecompressionSessionDecodeFrame:%x", rv)), __func__); return; } } void AppleVTDecoder::ProcessShutdown() { if (mSession) { LOG("%s: cleaning up session %p", __func__, mSession); VTDecompressionSessionInvalidate(mSession); CFRelease(mSession); mSession = nullptr; } if (mFormat) { LOG("%s: releasing format %p", __func__, mFormat); CFRelease(mFormat); mFormat = nullptr; } } RefPtr AppleVTDecoder::ProcessFlush() { AssertOnTaskQueue(); nsresult rv = WaitForAsynchronousFrames(); if (NS_FAILED(rv)) { LOG("AppleVTDecoder::Flush failed waiting for platform decoder"); } MonitorAutoLock mon(mMonitor); mPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); while (!mReorderQueue.IsEmpty()) { mReorderQueue.Pop(); } mPerformanceRecorder.Record(std::numeric_limits::max()); mSeekTargetThreshold.reset(); mIsFlushing = false; return FlushPromise::CreateAndResolve(true, __func__); } RefPtr AppleVTDecoder::ProcessDrain() { AssertOnTaskQueue(); nsresult rv = WaitForAsynchronousFrames(); if (NS_FAILED(rv)) { LOG("AppleVTDecoder::Drain failed waiting for platform decoder"); } MonitorAutoLock mon(mMonitor); DecodedData samples; while (!mReorderQueue.IsEmpty()) { samples.AppendElement(mReorderQueue.Pop()); } return DecodePromise::CreateAndResolve(std::move(samples), __func__); } AppleVTDecoder::AppleFrameRef* AppleVTDecoder::CreateAppleFrameRef( const MediaRawData* aSample) { MOZ_ASSERT(aSample); return new AppleFrameRef(*aSample); } void AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime) { if (aTime.IsValid()) { mSeekTargetThreshold = Some(aTime); } else { mSeekTargetThreshold.reset(); } } // // Implementation details. // // Callback passed to the VideoToolbox decoder for returning data. // This needs to be static because the API takes a C-style pair of // function and userdata pointers. This validates parameters and // forwards the decoded image back to an object method. static void PlatformCallback(void* decompressionOutputRefCon, void* sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags flags, CVImageBufferRef image, CMTime presentationTimeStamp, CMTime presentationDuration) { AppleVTDecoder* decoder = static_cast(decompressionOutputRefCon); LOGEX(decoder, "AppleVideoDecoder %s status %d flags %d", __func__, static_cast(status), flags); UniquePtr frameRef( static_cast(sourceFrameRefCon)); // Validate our arguments. if (status != noErr) { NS_WARNING("VideoToolbox decoder returned an error"); decoder->OnDecodeError(status); return; } else if (!image) { NS_WARNING("VideoToolbox decoder returned no data"); } else if (flags & kVTDecodeInfo_FrameDropped) { NS_WARNING(" ...frame tagged as dropped..."); } else { MOZ_ASSERT(CFGetTypeID(image) == CVPixelBufferGetTypeID(), "VideoToolbox returned an unexpected image type"); } decoder->OutputFrame(image, *frameRef); } void AppleVTDecoder::MaybeResolveBufferedFrames() { mMonitor.AssertCurrentThreadOwns(); if (mPromise.IsEmpty()) { return; } DecodedData results; while (mReorderQueue.Length() > mMaxRefFrames) { results.AppendElement(mReorderQueue.Pop()); } mPromise.Resolve(std::move(results), __func__); } void AppleVTDecoder::MaybeRegisterCallbackThread() { ProfilerThreadId id = profiler_current_thread_id(); if (MOZ_LIKELY(id == mCallbackThreadId)) { return; } mCallbackThreadId = id; CallbackThreadRegistry::Get()->Register(mCallbackThreadId, "AppleVTDecoderCallback"); } nsCString AppleVTDecoder::GetCodecName() const { switch (mStreamType) { case StreamType::H264: return "h264"_ns; case StreamType::VP9: return "vp9"_ns; default: return "unknown"_ns; } } // Copy and return a decoded frame. void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage, AppleVTDecoder::AppleFrameRef aFrameRef) { MaybeRegisterCallbackThread(); if (mIsFlushing) { // We are in the process of flushing or shutting down; ignore frame. return; } LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s", aFrameRef.byte_offset, aFrameRef.decode_timestamp.ToMicroseconds(), aFrameRef.composition_timestamp.ToMicroseconds(), aFrameRef.duration.ToMicroseconds(), aFrameRef.is_sync_point ? " keyframe" : ""); if (!aImage) { // Image was dropped by decoder or none return yet. // We need more input to continue. MonitorAutoLock mon(mMonitor); MaybeResolveBufferedFrames(); return; } bool useNullSample = false; if (mSeekTargetThreshold.isSome()) { if ((aFrameRef.composition_timestamp + aFrameRef.duration) < mSeekTargetThreshold.ref()) { useNullSample = true; } else { mSeekTargetThreshold.reset(); } } // Where our resulting image will end up. RefPtr data; // Bounds. VideoInfo info; info.mDisplay = gfx::IntSize(mDisplayWidth, mDisplayHeight); if (useNullSample) { data = new NullData(aFrameRef.byte_offset, aFrameRef.composition_timestamp, aFrameRef.duration); } else if (mUseSoftwareImages) { size_t width = CVPixelBufferGetWidth(aImage); size_t height = CVPixelBufferGetHeight(aImage); DebugOnly planes = CVPixelBufferGetPlaneCount(aImage); MOZ_ASSERT(planes == 3, "Likely not YUV420 format and it must be."); VideoData::YCbCrBuffer buffer; // Lock the returned image data. CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly); if (rv != kCVReturnSuccess) { NS_ERROR("error locking pixel data"); MonitorAutoLock mon(mMonitor); mPromise.Reject( MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, RESULT_DETAIL("CVPixelBufferLockBaseAddress:%x", rv)), __func__); return; } // Y plane. buffer.mPlanes[0].mData = static_cast(CVPixelBufferGetBaseAddressOfPlane(aImage, 0)); buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0); buffer.mPlanes[0].mWidth = width; buffer.mPlanes[0].mHeight = height; buffer.mPlanes[0].mSkip = 0; // Cb plane. buffer.mPlanes[1].mData = static_cast(CVPixelBufferGetBaseAddressOfPlane(aImage, 1)); buffer.mPlanes[1].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1); buffer.mPlanes[1].mWidth = (width + 1) / 2; buffer.mPlanes[1].mHeight = (height + 1) / 2; buffer.mPlanes[1].mSkip = 0; // Cr plane. buffer.mPlanes[2].mData = static_cast(CVPixelBufferGetBaseAddressOfPlane(aImage, 2)); buffer.mPlanes[2].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 2); buffer.mPlanes[2].mWidth = (width + 1) / 2; buffer.mPlanes[2].mHeight = (height + 1) / 2; buffer.mPlanes[2].mSkip = 0; buffer.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; buffer.mYUVColorSpace = mColorSpace; buffer.mColorPrimaries = mColorPrimaries; buffer.mColorRange = mColorRange; gfx::IntRect visible = gfx::IntRect(0, 0, mPictureWidth, mPictureHeight); // Copy the image data into our own format. data = VideoData::CreateAndCopyData( info, mImageContainer, aFrameRef.byte_offset, aFrameRef.composition_timestamp, aFrameRef.duration, buffer, aFrameRef.is_sync_point, aFrameRef.decode_timestamp, visible, mKnowsCompositor); // Unlock the returned image data. CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly); } else { #ifndef MOZ_WIDGET_UIKIT // Set pixel buffer properties on aImage before we extract its surface. // This ensures that we can use defined enums to set values instead // of later setting magic CFSTR values on the surface itself. if (mColorSpace == gfx::YUVColorSpace::BT601) { CVBufferSetAttachment(aImage, kCVImageBufferYCbCrMatrixKey, kCVImageBufferYCbCrMatrix_ITU_R_601_4, kCVAttachmentMode_ShouldPropagate); } else if (mColorSpace == gfx::YUVColorSpace::BT709) { CVBufferSetAttachment(aImage, kCVImageBufferYCbCrMatrixKey, kCVImageBufferYCbCrMatrix_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); } else if (mColorSpace == gfx::YUVColorSpace::BT2020) { CVBufferSetAttachment(aImage, kCVImageBufferYCbCrMatrixKey, kCVImageBufferYCbCrMatrix_ITU_R_2020, kCVAttachmentMode_ShouldPropagate); } if (mColorPrimaries == gfx::ColorSpace2::BT709) { CVBufferSetAttachment(aImage, kCVImageBufferColorPrimariesKey, kCVImageBufferColorPrimaries_ITU_R_709_2, kCVAttachmentMode_ShouldPropagate); } else if (mColorPrimaries == gfx::ColorSpace2::BT2020) { CVBufferSetAttachment(aImage, kCVImageBufferColorPrimariesKey, kCVImageBufferColorPrimaries_ITU_R_2020, kCVAttachmentMode_ShouldPropagate); } // Transfer function is applied independently from the colorSpace. CVBufferSetAttachment( aImage, kCVImageBufferTransferFunctionKey, gfxMacUtils::CFStringForTransferFunction(mTransferFunction), kCVAttachmentMode_ShouldPropagate); CFTypeRefPtr surface = CFTypeRefPtr::WrapUnderGetRule( CVPixelBufferGetIOSurface(aImage)); MOZ_ASSERT(surface, "Decoder didn't return an IOSurface backed buffer"); RefPtr macSurface = new MacIOSurface(std::move(surface)); macSurface->SetYUVColorSpace(mColorSpace); macSurface->mColorPrimaries = mColorPrimaries; RefPtr image = new layers::MacIOSurfaceImage(macSurface); data = VideoData::CreateFromImage( info.mDisplay, aFrameRef.byte_offset, aFrameRef.composition_timestamp, aFrameRef.duration, image.forget(), aFrameRef.is_sync_point, aFrameRef.decode_timestamp); #else MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS"); #endif } if (!data) { NS_ERROR("Couldn't create VideoData for frame"); MonitorAutoLock mon(mMonitor); mPromise.Reject(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); return; } mPerformanceRecorder.Record( aFrameRef.decode_timestamp.ToMicroseconds(), [&](DecodeStage& aStage) { aStage.SetResolution(static_cast(CVPixelBufferGetWidth(aImage)), static_cast(CVPixelBufferGetHeight(aImage))); auto format = [&]() -> Maybe { switch (CVPixelBufferGetPixelFormatType(aImage)) { case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: return Some(DecodeStage::NV12); case kCVPixelFormatType_422YpCbCr8_yuvs: case kCVPixelFormatType_422YpCbCr8FullRange: return Some(DecodeStage::YUV422P); case kCVPixelFormatType_32BGRA: return Some(DecodeStage::RGBA32); default: return Nothing(); } }(); format.apply([&](auto aFormat) { aStage.SetImageFormat(aFormat); }); aStage.SetColorDepth(mColorDepth); aStage.SetYUVColorSpace(mColorSpace); aStage.SetColorRange(mColorRange); }); // Frames come out in DTS order but we need to output them // in composition order. MonitorAutoLock mon(mMonitor); mReorderQueue.Push(std::move(data)); MaybeResolveBufferedFrames(); LOG("%llu decoded frames queued", static_cast(mReorderQueue.Length())); } void AppleVTDecoder::OnDecodeError(OSStatus aError) { MonitorAutoLock mon(mMonitor); mPromise.RejectIfExists( MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, RESULT_DETAIL("OnDecodeError:%x", aError)), __func__); } nsresult AppleVTDecoder::WaitForAsynchronousFrames() { OSStatus rv = VTDecompressionSessionWaitForAsynchronousFrames(mSession); if (rv != noErr) { NS_ERROR("AppleVTDecoder: Error waiting for asynchronous frames"); return NS_ERROR_FAILURE; } return NS_OK; } MediaResult AppleVTDecoder::InitializeSession() { OSStatus rv; AutoCFRelease extensions = CreateDecoderExtensions(); rv = CMVideoFormatDescriptionCreate( kCFAllocatorDefault, mStreamType == StreamType::H264 ? kCMVideoCodecType_H264 : CMVideoCodecType(AppleDecoderModule::kCMVideoCodecType_VP9), mPictureWidth, mPictureHeight, extensions, &mFormat); if (rv != noErr) { return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Couldn't create format description!")); } // Contruct video decoder selection spec. AutoCFRelease spec = CreateDecoderSpecification(); // Contruct output configuration. AutoCFRelease outputConfiguration = CreateOutputConfiguration(); VTDecompressionOutputCallbackRecord cb = {PlatformCallback, this}; rv = VTDecompressionSessionCreate(kCFAllocatorDefault, mFormat, spec, // Video decoder selection. outputConfiguration, // Output video format. &cb, &mSession); if (rv != noErr) { return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Couldn't create decompression session!")); } CFBooleanRef isUsingHW = nullptr; rv = VTSessionCopyProperty( mSession, kVTDecompressionPropertyKey_UsingHardwareAcceleratedVideoDecoder, kCFAllocatorDefault, &isUsingHW); if (rv == noErr) { mIsHardwareAccelerated = isUsingHW == kCFBooleanTrue; LOG("AppleVTDecoder: %s hardware accelerated decoding", mIsHardwareAccelerated ? "using" : "not using"); } else { LOG("AppleVTDecoder: maybe hardware accelerated decoding " "(VTSessionCopyProperty query failed)"); } if (isUsingHW) { CFRelease(isUsingHW); } return NS_OK; } CFDictionaryRef AppleVTDecoder::CreateDecoderExtensions() { AutoCFRelease data = CFDataCreate( kCFAllocatorDefault, mExtraData->Elements(), mExtraData->Length()); const void* atomsKey[1]; atomsKey[0] = mStreamType == StreamType::H264 ? CFSTR("avcC") : CFSTR("vpcC"); const void* atomsValue[] = {data}; static_assert(ArrayLength(atomsKey) == ArrayLength(atomsValue), "Non matching keys/values array size"); AutoCFRelease atoms = CFDictionaryCreate( kCFAllocatorDefault, atomsKey, atomsValue, ArrayLength(atomsKey), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); const void* extensionKeys[] = { kCVImageBufferChromaLocationBottomFieldKey, kCVImageBufferChromaLocationTopFieldKey, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms}; const void* extensionValues[] = {kCVImageBufferChromaLocation_Left, kCVImageBufferChromaLocation_Left, atoms}; static_assert(ArrayLength(extensionKeys) == ArrayLength(extensionValues), "Non matching keys/values array size"); return CFDictionaryCreate(kCFAllocatorDefault, extensionKeys, extensionValues, ArrayLength(extensionKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } CFDictionaryRef AppleVTDecoder::CreateDecoderSpecification() { const void* specKeys[] = { kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder}; const void* specValues[1]; if (gfx::gfxVars::CanUseHardwareVideoDecoding()) { specValues[0] = kCFBooleanTrue; } else { // This GPU is blacklisted for hardware decoding. specValues[0] = kCFBooleanFalse; } static_assert(ArrayLength(specKeys) == ArrayLength(specValues), "Non matching keys/values array size"); return CFDictionaryCreate( kCFAllocatorDefault, specKeys, specValues, ArrayLength(specKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } CFDictionaryRef AppleVTDecoder::CreateOutputConfiguration() { if (mUseSoftwareImages) { // Output format type: SInt32 PixelFormatTypeValue = kCVPixelFormatType_420YpCbCr8Planar; AutoCFRelease PixelFormatTypeNumber = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, &PixelFormatTypeValue); const void* outputKeys[] = {kCVPixelBufferPixelFormatTypeKey}; const void* outputValues[] = {PixelFormatTypeNumber}; static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues), "Non matching keys/values array size"); return CFDictionaryCreate( kCFAllocatorDefault, outputKeys, outputValues, ArrayLength(outputKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } #ifndef MOZ_WIDGET_UIKIT // Output format type: bool is10Bit = (gfx::BitDepthForColorDepth(mColorDepth) == 10); SInt32 PixelFormatTypeValue = mColorRange == gfx::ColorRange::FULL ? (is10Bit ? kCVPixelFormatType_420YpCbCr10BiPlanarFullRange : kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) : (is10Bit ? kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange); AutoCFRelease PixelFormatTypeNumber = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, &PixelFormatTypeValue); // Construct IOSurface Properties const void* IOSurfaceKeys[] = {kIOSurfaceIsGlobal}; const void* IOSurfaceValues[] = {kCFBooleanTrue}; static_assert(ArrayLength(IOSurfaceKeys) == ArrayLength(IOSurfaceValues), "Non matching keys/values array size"); // Contruct output configuration. AutoCFRelease IOSurfaceProperties = CFDictionaryCreate( kCFAllocatorDefault, IOSurfaceKeys, IOSurfaceValues, ArrayLength(IOSurfaceKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); const void* outputKeys[] = {kCVPixelBufferIOSurfacePropertiesKey, kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferOpenGLCompatibilityKey}; const void* outputValues[] = {IOSurfaceProperties, PixelFormatTypeNumber, kCFBooleanTrue}; static_assert(ArrayLength(outputKeys) == ArrayLength(outputValues), "Non matching keys/values array size"); return CFDictionaryCreate( kCFAllocatorDefault, outputKeys, outputValues, ArrayLength(outputKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); #else MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS"); #endif } } // namespace mozilla #undef LOG #undef LOGEX