// Copyright 2013 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. #import "media/base/mac/avfoundation_glue.h" #include #include "base/command_line.h" #include "base/lazy_instance.h" #include "base/mac/mac_util.h" #include "base/metrics/field_trial.h" #include "media/base/media_switches.h" namespace { // This class is used to retrieve AVFoundation NSBundle and library handle. It // must be used as a LazyInstance so that it is initialised once and in a // thread-safe way. Normally no work is done in constructors: LazyInstance is // an exception. class AVFoundationInternal { public: AVFoundationInternal() { bundle_ = [NSBundle bundleWithPath:@"/System/Library/Frameworks/AVFoundation.framework"]; const char* path = [[bundle_ executablePath] fileSystemRepresentation]; CHECK(path); library_handle_ = dlopen(path, RTLD_LAZY | RTLD_LOCAL); CHECK(library_handle_) << dlerror(); struct { NSString** loaded_string; const char* symbol; } av_strings[] = { {&AVCaptureDeviceWasConnectedNotification_, "AVCaptureDeviceWasConnectedNotification"}, {&AVCaptureDeviceWasDisconnectedNotification_, "AVCaptureDeviceWasDisconnectedNotification"}, {&AVMediaTypeVideo_, "AVMediaTypeVideo"}, {&AVMediaTypeAudio_, "AVMediaTypeAudio"}, {&AVMediaTypeMuxed_, "AVMediaTypeMuxed"}, {&AVCaptureSessionRuntimeErrorNotification_, "AVCaptureSessionRuntimeErrorNotification"}, {&AVCaptureSessionDidStopRunningNotification_, "AVCaptureSessionDidStopRunningNotification"}, {&AVCaptureSessionErrorKey_, "AVCaptureSessionErrorKey"}, {&AVVideoScalingModeKey_, "AVVideoScalingModeKey"}, {&AVVideoScalingModeResizeAspectFill_, "AVVideoScalingModeResizeAspectFill"}, }; for (size_t i = 0; i < arraysize(av_strings); ++i) { *av_strings[i].loaded_string = *reinterpret_cast( dlsym(library_handle_, av_strings[i].symbol)); DCHECK(*av_strings[i].loaded_string) << dlerror(); } } NSBundle* bundle() const { return bundle_; } void* library_handle() const { return library_handle_; } NSString* AVCaptureDeviceWasConnectedNotification() const { return AVCaptureDeviceWasConnectedNotification_; } NSString* AVCaptureDeviceWasDisconnectedNotification() const { return AVCaptureDeviceWasDisconnectedNotification_; } NSString* AVMediaTypeVideo() const { return AVMediaTypeVideo_; } NSString* AVMediaTypeAudio() const { return AVMediaTypeAudio_; } NSString* AVMediaTypeMuxed() const { return AVMediaTypeMuxed_; } NSString* AVCaptureSessionRuntimeErrorNotification() const { return AVCaptureSessionRuntimeErrorNotification_; } NSString* AVCaptureSessionDidStopRunningNotification() const { return AVCaptureSessionDidStopRunningNotification_; } NSString* AVCaptureSessionErrorKey() const { return AVCaptureSessionErrorKey_; } NSString* AVVideoScalingModeKey() const { return AVVideoScalingModeKey_; } NSString* AVVideoScalingModeResizeAspectFill() const { return AVVideoScalingModeResizeAspectFill_; } private: NSBundle* bundle_; void* library_handle_; // The following members are replicas of the respectives in AVFoundation. NSString* AVCaptureDeviceWasConnectedNotification_; NSString* AVCaptureDeviceWasDisconnectedNotification_; NSString* AVMediaTypeVideo_; NSString* AVMediaTypeAudio_; NSString* AVMediaTypeMuxed_; NSString* AVCaptureSessionRuntimeErrorNotification_; NSString* AVCaptureSessionDidStopRunningNotification_; NSString* AVCaptureSessionErrorKey_; NSString* AVVideoScalingModeKey_; NSString* AVVideoScalingModeResizeAspectFill_; DISALLOW_COPY_AND_ASSIGN(AVFoundationInternal); }; } // namespace static base::LazyInstance g_avfoundation_handle = LAZY_INSTANCE_INITIALIZER; bool AVFoundationGlue::IsAVFoundationSupported() { // DeviceMonitorMac will initialize this static bool from the main UI thread // once, during Chrome startup so this construction is thread safe. // Use AVFoundation if possible, enabled, and QTKit is not explicitly forced. static CommandLine* command_line = CommandLine::ForCurrentProcess(); // AVFoundation is only available on OS Lion and above. if (!base::mac::IsOSLionOrLater()) return false; // The force-qtkit flag takes precedence over enable-avfoundation. if (command_line->HasSwitch(switches::kForceQTKit)) return false; // Next in precedence is the enable-avfoundation flag. // TODO(vrk): Does this really need to be static? static bool should_enable_avfoundation = command_line->HasSwitch(switches::kEnableAVFoundation) || base::FieldTrialList::FindFullName("AVFoundationMacVideoCapture") == "Enabled"; // Try to load AVFoundation. Save result in static bool to avoid loading // AVFoundationBundle every call. static bool loaded_successfully = [AVFoundationBundle() load]; return should_enable_avfoundation && loaded_successfully; } NSBundle const* AVFoundationGlue::AVFoundationBundle() { return g_avfoundation_handle.Get().bundle(); } void* AVFoundationGlue::AVFoundationLibraryHandle() { return g_avfoundation_handle.Get().library_handle(); } NSString* AVFoundationGlue::AVCaptureDeviceWasConnectedNotification() { return g_avfoundation_handle.Get().AVCaptureDeviceWasConnectedNotification(); } NSString* AVFoundationGlue::AVCaptureDeviceWasDisconnectedNotification() { return g_avfoundation_handle.Get().AVCaptureDeviceWasDisconnectedNotification(); } NSString* AVFoundationGlue::AVMediaTypeVideo() { return g_avfoundation_handle.Get().AVMediaTypeVideo(); } NSString* AVFoundationGlue::AVMediaTypeAudio() { return g_avfoundation_handle.Get().AVMediaTypeAudio(); } NSString* AVFoundationGlue::AVMediaTypeMuxed() { return g_avfoundation_handle.Get().AVMediaTypeMuxed(); } NSString* AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification() { return g_avfoundation_handle.Get().AVCaptureSessionRuntimeErrorNotification(); } NSString* AVFoundationGlue::AVCaptureSessionDidStopRunningNotification() { return g_avfoundation_handle.Get().AVCaptureSessionDidStopRunningNotification(); } NSString* AVFoundationGlue::AVCaptureSessionErrorKey() { return g_avfoundation_handle.Get().AVCaptureSessionErrorKey(); } NSString* AVFoundationGlue::AVVideoScalingModeKey() { return g_avfoundation_handle.Get().AVVideoScalingModeKey(); } NSString* AVFoundationGlue::AVVideoScalingModeResizeAspectFill() { return g_avfoundation_handle.Get().AVVideoScalingModeResizeAspectFill(); } Class AVFoundationGlue::AVCaptureSessionClass() { return [AVFoundationBundle() classNamed:@"AVCaptureSession"]; } Class AVFoundationGlue::AVCaptureVideoDataOutputClass() { return [AVFoundationBundle() classNamed:@"AVCaptureVideoDataOutput"]; } @implementation AVCaptureDeviceGlue + (NSArray*)devices { Class avcClass = [AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDevice"]; if ([avcClass respondsToSelector:@selector(devices)]) { return [avcClass performSelector:@selector(devices)]; } return nil; } + (CrAVCaptureDevice*)deviceWithUniqueID:(NSString*)deviceUniqueID { Class avcClass = [AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDevice"]; return [avcClass performSelector:@selector(deviceWithUniqueID:) withObject:deviceUniqueID]; } @end // @implementation AVCaptureDeviceGlue @implementation AVCaptureDeviceInputGlue + (CrAVCaptureDeviceInput*)deviceInputWithDevice:(CrAVCaptureDevice*)device error:(NSError**)outError { return [[AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDeviceInput"] deviceInputWithDevice:device error:outError]; } @end // @implementation AVCaptureDeviceInputGlue