// Copyright (c) 2012 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 "ui/gfx/video_decode_acceleration_support_mac.h" #include #include "base/bind.h" #include "base/file_path.h" #import "base/mac/foundation_util.h" #include "base/location.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/native_library.h" using base::mac::CFToNSCast; using base::mac::NSToCFCast; const CFStringRef kVDADecoderConfiguration_Width = CFSTR("width"); const CFStringRef kVDADecoderConfiguration_Height = CFSTR("height"); const CFStringRef kVDADecoderConfiguration_SourceFormat = CFSTR("format"); const CFStringRef kVDADecoderConfiguration_avcCData = CFSTR("avcC"); const CFStringRef kCVPixelBufferIOSurfacePropertiesKey = CFSTR("IOSurfaceProperties"); namespace { // These are 10.6.3 APIs that we look up at run time. typedef OSStatus (*VDADecoderCreateFunc)( CFDictionaryRef decoderConfiguration, CFDictionaryRef destinationImageBufferAttributes, VDADecoderOutputCallback* outputCallback, void* decoderOutputCallbackRefcon, VDADecoder* decoderOut); typedef OSStatus (*VDADecoderDecodeFunc)( VDADecoder decoder, uint32_t decodeFlags, CFTypeRef compressedBuffer, CFDictionaryRef frameInfo); typedef OSStatus (*VDADecoderFlushFunc)( VDADecoder decoder, uint32_t flushFlags); typedef OSStatus (*VDADecoderDestroyFunc)( VDADecoder decoder); VDADecoderCreateFunc g_VDADecoderCreate; VDADecoderDecodeFunc g_VDADecoderDecode; VDADecoderFlushFunc g_VDADecoderFlush; VDADecoderDestroyFunc g_VDADecoderDestroy; NSString* const kFrameIdKey = @"frame_id"; bool InitializeVdaApis() { static bool should_initialize = true; if (should_initialize) { should_initialize = false; FilePath path; if (!base::mac::GetSearchPathDirectory( NSLibraryDirectory, NSSystemDomainMask, &path)) { return false; } path = path.AppendASCII("Frameworks") .AppendASCII("VideoDecodeAcceleration.framework") .AppendASCII("VideoDecodeAcceleration"); base::NativeLibrary vda_library = base::LoadNativeLibrary(path, NULL); if (!vda_library) return false; g_VDADecoderCreate = reinterpret_cast( base::GetFunctionPointerFromNativeLibrary( vda_library, "VDADecoderCreate")); g_VDADecoderDecode = reinterpret_cast( base::GetFunctionPointerFromNativeLibrary( vda_library, "VDADecoderDecode")); g_VDADecoderFlush = reinterpret_cast( base::GetFunctionPointerFromNativeLibrary( vda_library, "VDADecoderFlush")); g_VDADecoderDestroy = reinterpret_cast( base::GetFunctionPointerFromNativeLibrary( vda_library, "VDADecoderDestroy")); } return g_VDADecoderCreate && g_VDADecoderDecode && g_VDADecoderFlush && g_VDADecoderDestroy; } } // namespace namespace gfx { VideoDecodeAccelerationSupport::VideoDecodeAccelerationSupport() : decoder_(NULL), frame_id_count_(0), loop_(base::MessageLoopProxy::current()) { } VideoDecodeAccelerationSupport::Status VideoDecodeAccelerationSupport::Create( int width, int height, int pixel_format, const void* avc_bytes, size_t avc_size) { if (!InitializeVdaApis()) return LOAD_FRAMEWORK_ERROR; NSData* avc_data = [NSData dataWithBytes:avc_bytes length:avc_size]; NSDictionary* config = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:width], CFToNSCast(kVDADecoderConfiguration_Width), [NSNumber numberWithInt:height], CFToNSCast(kVDADecoderConfiguration_Height), [NSNumber numberWithInt:'avc1'], CFToNSCast(kVDADecoderConfiguration_SourceFormat), avc_data, CFToNSCast(kVDADecoderConfiguration_avcCData), nil]; NSDictionary* format_info = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:pixel_format], CFToNSCast(kCVPixelBufferPixelFormatTypeKey), // Must set to an empty dictionary as per documentation. [NSDictionary dictionary], CFToNSCast(kCVPixelBufferIOSurfacePropertiesKey), nil]; OSStatus status = g_VDADecoderCreate(NSToCFCast(config), NSToCFCast(format_info), reinterpret_cast(OnFrameReadyCallback), this, &decoder_); switch (status) { case kVDADecoderNoErr: return SUCCESS; case kVDADecoderHardwareNotSupportedErr: return HARDWARE_NOT_SUPPORTED_ERROR; default: return OTHER_ERROR; } } VideoDecodeAccelerationSupport::Status VideoDecodeAccelerationSupport::Decode( const void* bytes, size_t size, const FrameReadyCallback& cb) { DCHECK(decoder_); ++frame_id_count_; frame_ready_callbacks_[frame_id_count_] = cb; NSData* data = [NSData dataWithBytes:bytes length:size]; NSDictionary* frame_info = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:frame_id_count_] forKey:kFrameIdKey]; OSStatus status = g_VDADecoderDecode(decoder_, 0, NSToCFCast(data), NSToCFCast(frame_info)); if (status != kVDADecoderNoErr) { frame_ready_callbacks_.erase(frame_id_count_); return OTHER_ERROR; } return SUCCESS; } VideoDecodeAccelerationSupport::Status VideoDecodeAccelerationSupport::Flush( bool emit_frames) { DCHECK(decoder_); OSStatus status = g_VDADecoderFlush( decoder_, emit_frames ? kVDADecoderFlush_EmitFrames : 0); if (!emit_frames) frame_ready_callbacks_.clear(); return status == kVDADecoderNoErr ? SUCCESS : OTHER_ERROR; } VideoDecodeAccelerationSupport::Status VideoDecodeAccelerationSupport::Destroy() { // Decoder might be NULL if Create() fails. if (!decoder_) return SUCCESS; OSStatus status = g_VDADecoderDestroy(decoder_); decoder_ = NULL; return status == kVDADecoderNoErr ? SUCCESS : OTHER_ERROR; } VideoDecodeAccelerationSupport::~VideoDecodeAccelerationSupport() { DCHECK(!decoder_); } // static void VideoDecodeAccelerationSupport::OnFrameReadyCallback( void* callback_data, CFDictionaryRef frame_info, OSStatus status, uint32_t flags, CVImageBufferRef image_buffer) { base::mac::ScopedNSAutoreleasePool pool; NSNumber* frame_id_num = base::mac::ObjCCastStrict( [CFToNSCast(frame_info) objectForKey:kFrameIdKey]); int frame_id = [frame_id_num intValue]; // Retain the image while the callback is in flight. if (image_buffer) CFRetain(image_buffer); VideoDecodeAccelerationSupport* vda = reinterpret_cast(callback_data); DCHECK(vda); vda->loop_->PostTask( FROM_HERE, base::Bind(&VideoDecodeAccelerationSupport::OnFrameReady, vda, image_buffer, status, frame_id)); } void VideoDecodeAccelerationSupport::OnFrameReady(CVImageBufferRef image_buffer, OSStatus status, int frame_id) { frame_ready_callbacks_[frame_id].Run(image_buffer, status); // Match the retain in OnFrameReadyCallback. if (image_buffer) CFRelease(image_buffer); frame_ready_callbacks_.erase(frame_id); } } // namespace