1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "media/base/mac/avfoundation_glue.h"
6
7#include <dlfcn.h>
8
9#include "base/command_line.h"
10#include "base/lazy_instance.h"
11#include "base/mac/mac_util.h"
12#include "base/metrics/field_trial.h"
13#include "media/base/media_switches.h"
14
15namespace {
16
17// This class is used to retrieve AVFoundation NSBundle and library handle. It
18// must be used as a LazyInstance so that it is initialised once and in a
19// thread-safe way. Normally no work is done in constructors: LazyInstance is
20// an exception.
21class AVFoundationInternal {
22 public:
23  AVFoundationInternal() {
24    bundle_ = [NSBundle
25        bundleWithPath:@"/System/Library/Frameworks/AVFoundation.framework"];
26
27    const char* path = [[bundle_ executablePath] fileSystemRepresentation];
28    CHECK(path);
29    library_handle_ = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
30    CHECK(library_handle_) << dlerror();
31
32    struct {
33      NSString** loaded_string;
34      const char* symbol;
35    } av_strings[] = {
36        {&AVCaptureDeviceWasConnectedNotification_,
37         "AVCaptureDeviceWasConnectedNotification"},
38        {&AVCaptureDeviceWasDisconnectedNotification_,
39         "AVCaptureDeviceWasDisconnectedNotification"},
40        {&AVMediaTypeVideo_, "AVMediaTypeVideo"},
41        {&AVMediaTypeAudio_, "AVMediaTypeAudio"},
42        {&AVMediaTypeMuxed_, "AVMediaTypeMuxed"},
43        {&AVCaptureSessionRuntimeErrorNotification_,
44         "AVCaptureSessionRuntimeErrorNotification"},
45        {&AVCaptureSessionDidStopRunningNotification_,
46         "AVCaptureSessionDidStopRunningNotification"},
47        {&AVCaptureSessionErrorKey_, "AVCaptureSessionErrorKey"},
48        {&AVVideoScalingModeKey_, "AVVideoScalingModeKey"},
49        {&AVVideoScalingModeResizeAspectFill_,
50         "AVVideoScalingModeResizeAspectFill"},
51    };
52    for (size_t i = 0; i < arraysize(av_strings); ++i) {
53      *av_strings[i].loaded_string = *reinterpret_cast<NSString**>(
54          dlsym(library_handle_, av_strings[i].symbol));
55      DCHECK(*av_strings[i].loaded_string) << dlerror();
56    }
57  }
58
59  NSBundle* bundle() const { return bundle_; }
60  void* library_handle() const { return library_handle_; }
61
62  NSString* AVCaptureDeviceWasConnectedNotification() const {
63    return AVCaptureDeviceWasConnectedNotification_;
64  }
65  NSString* AVCaptureDeviceWasDisconnectedNotification() const {
66    return AVCaptureDeviceWasDisconnectedNotification_;
67  }
68  NSString* AVMediaTypeVideo() const { return AVMediaTypeVideo_; }
69  NSString* AVMediaTypeAudio() const { return AVMediaTypeAudio_; }
70  NSString* AVMediaTypeMuxed() const { return AVMediaTypeMuxed_; }
71  NSString* AVCaptureSessionRuntimeErrorNotification() const {
72    return AVCaptureSessionRuntimeErrorNotification_;
73  }
74  NSString* AVCaptureSessionDidStopRunningNotification() const {
75    return AVCaptureSessionDidStopRunningNotification_;
76  }
77  NSString* AVCaptureSessionErrorKey() const {
78    return AVCaptureSessionErrorKey_;
79  }
80  NSString* AVVideoScalingModeKey() const { return AVVideoScalingModeKey_; }
81  NSString* AVVideoScalingModeResizeAspectFill() const {
82    return AVVideoScalingModeResizeAspectFill_;
83  }
84
85 private:
86  NSBundle* bundle_;
87  void* library_handle_;
88  // The following members are replicas of the respectives in AVFoundation.
89  NSString* AVCaptureDeviceWasConnectedNotification_;
90  NSString* AVCaptureDeviceWasDisconnectedNotification_;
91  NSString* AVMediaTypeVideo_;
92  NSString* AVMediaTypeAudio_;
93  NSString* AVMediaTypeMuxed_;
94  NSString* AVCaptureSessionRuntimeErrorNotification_;
95  NSString* AVCaptureSessionDidStopRunningNotification_;
96  NSString* AVCaptureSessionErrorKey_;
97  NSString* AVVideoScalingModeKey_;
98  NSString* AVVideoScalingModeResizeAspectFill_;
99
100  DISALLOW_COPY_AND_ASSIGN(AVFoundationInternal);
101};
102
103}  // namespace
104
105static base::LazyInstance<AVFoundationInternal> g_avfoundation_handle =
106    LAZY_INSTANCE_INITIALIZER;
107
108bool AVFoundationGlue::IsAVFoundationSupported() {
109  // DeviceMonitorMac will initialize this static bool from the main UI thread
110  // once, during Chrome startup so this construction is thread safe.
111  // Use AVFoundation if possible, enabled, and QTKit is not explicitly forced.
112  static CommandLine* command_line = CommandLine::ForCurrentProcess();
113
114  // AVFoundation is only available on OS Lion and above.
115  if (!base::mac::IsOSLionOrLater())
116    return false;
117
118  // The force-qtkit flag takes precedence over enable-avfoundation.
119  if (command_line->HasSwitch(switches::kForceQTKit))
120    return false;
121
122  // Next in precedence is the enable-avfoundation flag.
123  // TODO(vrk): Does this really need to be static?
124  static bool should_enable_avfoundation =
125      command_line->HasSwitch(switches::kEnableAVFoundation) ||
126      base::FieldTrialList::FindFullName("AVFoundationMacVideoCapture")
127          == "Enabled";
128  // Try to load AVFoundation. Save result in static bool to avoid loading
129  // AVFoundationBundle every call.
130  static bool loaded_successfully = [AVFoundationBundle() load];
131  return should_enable_avfoundation && loaded_successfully;
132}
133
134NSBundle const* AVFoundationGlue::AVFoundationBundle() {
135  return g_avfoundation_handle.Get().bundle();
136}
137
138void* AVFoundationGlue::AVFoundationLibraryHandle() {
139  return g_avfoundation_handle.Get().library_handle();
140}
141
142NSString* AVFoundationGlue::AVCaptureDeviceWasConnectedNotification() {
143  return g_avfoundation_handle.Get().AVCaptureDeviceWasConnectedNotification();
144}
145
146NSString* AVFoundationGlue::AVCaptureDeviceWasDisconnectedNotification() {
147  return
148      g_avfoundation_handle.Get().AVCaptureDeviceWasDisconnectedNotification();
149}
150
151NSString* AVFoundationGlue::AVMediaTypeVideo() {
152  return g_avfoundation_handle.Get().AVMediaTypeVideo();
153}
154
155NSString* AVFoundationGlue::AVMediaTypeAudio() {
156  return g_avfoundation_handle.Get().AVMediaTypeAudio();
157}
158
159NSString* AVFoundationGlue::AVMediaTypeMuxed() {
160  return g_avfoundation_handle.Get().AVMediaTypeMuxed();
161}
162
163NSString* AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification() {
164  return g_avfoundation_handle.Get().AVCaptureSessionRuntimeErrorNotification();
165}
166
167NSString* AVFoundationGlue::AVCaptureSessionDidStopRunningNotification() {
168  return
169      g_avfoundation_handle.Get().AVCaptureSessionDidStopRunningNotification();
170}
171
172NSString* AVFoundationGlue::AVCaptureSessionErrorKey() {
173  return g_avfoundation_handle.Get().AVCaptureSessionErrorKey();
174}
175
176NSString* AVFoundationGlue::AVVideoScalingModeKey() {
177  return g_avfoundation_handle.Get().AVVideoScalingModeKey();
178}
179
180NSString* AVFoundationGlue::AVVideoScalingModeResizeAspectFill() {
181  return g_avfoundation_handle.Get().AVVideoScalingModeResizeAspectFill();
182}
183
184Class AVFoundationGlue::AVCaptureSessionClass() {
185  return [AVFoundationBundle() classNamed:@"AVCaptureSession"];
186}
187
188Class AVFoundationGlue::AVCaptureVideoDataOutputClass() {
189  return [AVFoundationBundle() classNamed:@"AVCaptureVideoDataOutput"];
190}
191
192@implementation AVCaptureDeviceGlue
193
194+ (NSArray*)devices {
195  Class avcClass =
196      [AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDevice"];
197  if ([avcClass respondsToSelector:@selector(devices)]) {
198    return [avcClass performSelector:@selector(devices)];
199  }
200  return nil;
201}
202
203+ (CrAVCaptureDevice*)deviceWithUniqueID:(NSString*)deviceUniqueID {
204  Class avcClass =
205      [AVFoundationGlue::AVFoundationBundle() classNamed:@"AVCaptureDevice"];
206  return [avcClass performSelector:@selector(deviceWithUniqueID:)
207                        withObject:deviceUniqueID];
208}
209
210@end  // @implementation AVCaptureDeviceGlue
211
212@implementation AVCaptureDeviceInputGlue
213
214+ (CrAVCaptureDeviceInput*)deviceInputWithDevice:(CrAVCaptureDevice*)device
215                                           error:(NSError**)outError {
216  return [[AVFoundationGlue::AVFoundationBundle()
217      classNamed:@"AVCaptureDeviceInput"] deviceInputWithDevice:device
218                                                          error:outError];
219}
220
221@end  // @implementation AVCaptureDeviceInputGlue
222