1 // Copyright 2014 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.
5 #include "media/capture/video/mac/video_capture_device_factory_mac.h"
7 #import <IOKit/audio/IOAudioTypes.h>
10 #include "base/location.h"
11 #include "base/profiler/scoped_tracker.h"
12 #include "base/strings/string_util.h"
13 #include "base/task_runner_util.h"
14 #import "media/base/mac/avfoundation_glue.h"
15 #import "media/capture/video/mac/video_capture_device_avfoundation_mac.h"
16 #import "media/capture/video/mac/video_capture_device_decklink_mac.h"
17 #include "media/capture/video/mac/video_capture_device_mac.h"
18 #import "media/capture/video/mac/video_capture_device_qtkit_mac.h"
22 // In QTKit API, some devices are known to crash if VGA is requested, for them
23 // HD is the only supported resolution (see http://crbug.com/396812). In the
24 // AVfoundation case, we skip enumerating them altogether. These devices are
25 // identified by a characteristic trailing substring of uniqueId. At the moment
26 // these are just Blackmagic devices.
27 const struct NameAndVid {
28 const char* unique_id_signature;
29 const int capture_width;
30 const int capture_height;
31 const float capture_frame_rate;
32 } kBlacklistedCameras[] = {{"-01FDA82C8A9C", 1280, 720, 60.0f}};
34 static bool IsDeviceBlacklisted(const VideoCaptureDevice::Name& name) {
35 bool is_device_blacklisted = false;
37 !is_device_blacklisted && i < arraysize(kBlacklistedCameras); ++i) {
38 is_device_blacklisted =
39 base::EndsWith(name.id(),
40 kBlacklistedCameras[i].unique_id_signature,
41 base::CompareCase::INSENSITIVE_ASCII);
43 DVLOG_IF(2, is_device_blacklisted) << "Blacklisted camera: " << name.name()
44 << ", id: " << name.id();
45 return is_device_blacklisted;
48 static scoped_ptr<media::VideoCaptureDevice::Names>
49 EnumerateDevicesUsingQTKit() {
50 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
52 tracked_objects::ScopedTracker tracking_profile(
53 FROM_HERE_WITH_EXPLICIT_FUNCTION(
54 "458397 media::EnumerateDevicesUsingQTKit"));
56 scoped_ptr<VideoCaptureDevice::Names> device_names(
57 new VideoCaptureDevice::Names());
58 NSMutableDictionary* capture_devices =
59 [[[NSMutableDictionary alloc] init] autorelease];
60 [VideoCaptureDeviceQTKit getDeviceNames:capture_devices];
61 for (NSString* key in capture_devices) {
62 VideoCaptureDevice::Name name(
63 [[[capture_devices valueForKey:key] deviceName] UTF8String],
64 [key UTF8String], VideoCaptureDevice::Name::QTKIT);
65 if (IsDeviceBlacklisted(name))
66 name.set_is_blacklisted(true);
67 device_names->push_back(name);
69 return device_names.Pass();
72 static void RunDevicesEnumeratedCallback(
73 const base::Callback<void(scoped_ptr<media::VideoCaptureDevice::Names>)>&
75 scoped_ptr<media::VideoCaptureDevice::Names> device_names) {
76 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
78 tracked_objects::ScopedTracker tracking_profile(
79 FROM_HERE_WITH_EXPLICIT_FUNCTION(
80 "458397 media::RunDevicesEnumeratedCallback"));
81 callback.Run(device_names.Pass());
85 bool VideoCaptureDeviceFactoryMac::PlatformSupportsAVFoundation() {
86 return AVFoundationGlue::IsAVFoundationSupported();
89 VideoCaptureDeviceFactoryMac::VideoCaptureDeviceFactoryMac(
90 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
91 : ui_task_runner_(ui_task_runner) {
92 thread_checker_.DetachFromThread();
95 VideoCaptureDeviceFactoryMac::~VideoCaptureDeviceFactoryMac() {
98 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryMac::Create(
99 const VideoCaptureDevice::Name& device_name) {
100 DCHECK(thread_checker_.CalledOnValidThread());
101 DCHECK_NE(device_name.capture_api_type(),
102 VideoCaptureDevice::Name::API_TYPE_UNKNOWN);
104 scoped_ptr<VideoCaptureDevice> capture_device;
105 if (device_name.capture_api_type() == VideoCaptureDevice::Name::DECKLINK) {
106 capture_device.reset(new VideoCaptureDeviceDeckLinkMac(device_name));
108 VideoCaptureDeviceMac* device = new VideoCaptureDeviceMac(device_name);
109 capture_device.reset(device);
110 if (!device->Init(device_name.capture_api_type())) {
111 LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
112 capture_device.reset();
115 return scoped_ptr<VideoCaptureDevice>(capture_device.Pass());
118 void VideoCaptureDeviceFactoryMac::GetDeviceNames(
119 VideoCaptureDevice::Names* device_names) {
120 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
122 tracked_objects::ScopedTracker tracking_profile(
123 FROM_HERE_WITH_EXPLICIT_FUNCTION(
124 "458397 VideoCaptureDeviceFactoryMac::GetDeviceNames"));
125 DCHECK(thread_checker_.CalledOnValidThread());
126 // Loop through all available devices and add to |device_names|.
127 NSDictionary* capture_devices;
128 if (AVFoundationGlue::IsAVFoundationSupported()) {
129 DVLOG(1) << "Enumerating video capture devices using AVFoundation";
130 capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
131 // Enumerate all devices found by AVFoundation, translate the info for each
132 // to class Name and add it to |device_names|.
133 for (NSString* key in capture_devices) {
134 int transport_type = [[capture_devices valueForKey:key] transportType];
135 // Transport types are defined for Audio devices and reused for video.
136 VideoCaptureDevice::Name::TransportType device_transport_type =
137 (transport_type == kIOAudioDeviceTransportTypeBuiltIn ||
138 transport_type == kIOAudioDeviceTransportTypeUSB)
139 ? VideoCaptureDevice::Name::USB_OR_BUILT_IN
140 : VideoCaptureDevice::Name::OTHER_TRANSPORT;
141 VideoCaptureDevice::Name name(
142 [[[capture_devices valueForKey:key] deviceName] UTF8String],
143 [key UTF8String], VideoCaptureDevice::Name::AVFOUNDATION,
144 device_transport_type);
145 if (IsDeviceBlacklisted(name))
147 device_names->push_back(name);
149 // Also retrieve Blackmagic devices, if present, via DeckLink SDK API.
150 VideoCaptureDeviceDeckLinkMac::EnumerateDevices(device_names);
152 // We should not enumerate QTKit devices in Device Thread;
157 void VideoCaptureDeviceFactoryMac::EnumerateDeviceNames(const base::Callback<
158 void(scoped_ptr<media::VideoCaptureDevice::Names>)>& callback) {
159 DCHECK(thread_checker_.CalledOnValidThread());
160 if (AVFoundationGlue::IsAVFoundationSupported()) {
161 scoped_ptr<VideoCaptureDevice::Names> device_names(
162 new VideoCaptureDevice::Names());
163 GetDeviceNames(device_names.get());
164 callback.Run(device_names.Pass());
166 DVLOG(1) << "Enumerating video capture devices using QTKit";
167 base::PostTaskAndReplyWithResult(
168 ui_task_runner_.get(), FROM_HERE,
169 base::Bind(&EnumerateDevicesUsingQTKit),
170 base::Bind(&RunDevicesEnumeratedCallback, callback));
174 void VideoCaptureDeviceFactoryMac::GetDeviceSupportedFormats(
175 const VideoCaptureDevice::Name& device,
176 VideoCaptureFormats* supported_formats) {
177 DCHECK(thread_checker_.CalledOnValidThread());
178 switch (device.capture_api_type()) {
179 case VideoCaptureDevice::Name::AVFOUNDATION:
180 DVLOG(1) << "Enumerating video capture capabilities, AVFoundation";
181 [VideoCaptureDeviceAVFoundation getDevice:device
182 supportedFormats:supported_formats];
184 case VideoCaptureDevice::Name::QTKIT:
185 // Blacklisted cameras provide their own supported format(s), otherwise no
186 // such information is provided for QTKit devices.
187 if (device.is_blacklisted()) {
188 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
189 if (base::EndsWith(device.id(),
190 kBlacklistedCameras[i].unique_id_signature,
191 base::CompareCase::INSENSITIVE_ASCII)) {
192 supported_formats->push_back(media::VideoCaptureFormat(
193 gfx::Size(kBlacklistedCameras[i].capture_width,
194 kBlacklistedCameras[i].capture_height),
195 kBlacklistedCameras[i].capture_frame_rate,
196 media::PIXEL_FORMAT_UYVY));
202 case VideoCaptureDevice::Name::DECKLINK:
203 DVLOG(1) << "Enumerating video capture capabilities " << device.name();
204 VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities(
205 device, supported_formats);
213 VideoCaptureDeviceFactory*
214 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
215 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
216 return new VideoCaptureDeviceFactoryMac(ui_task_runner);