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/video/capture/mac/video_capture_device_factory_mac.h"
7 #import <IOKit/audio/IOAudioTypes.h>
10 #include "base/location.h"
11 #include "base/strings/string_util.h"
12 #include "base/task_runner_util.h"
13 #import "media/base/mac/avfoundation_glue.h"
14 #import "media/video/capture/mac/video_capture_device_avfoundation_mac.h"
15 #include "media/video/capture/mac/video_capture_device_mac.h"
16 #import "media/video/capture/mac/video_capture_device_decklink_mac.h"
17 #import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
21 // Some devices are not correctly supported in AVFoundation, f.i. Blackmagic,
22 // see http://crbug.com/347371. The devices are identified by a characteristic
23 // trailing substring of uniqueId and by (part of) the vendor's name.
24 // Blackmagic cameras are known to crash if VGA is requested , see
25 // http://crbug.com/396812; for them HD is the only supported resolution.
26 const struct NameAndVid {
27 const char* unique_id_signature;
29 const int capture_width;
30 const int capture_height;
31 const float capture_frame_rate;
32 } kBlacklistedCameras[] = {
33 { "-01FDA82C8A9C", "Blackmagic", 1280, 720, 60.0f } };
35 static scoped_ptr<media::VideoCaptureDevice::Names>
36 EnumerateDevicesUsingQTKit() {
37 scoped_ptr<VideoCaptureDevice::Names> device_names(
38 new VideoCaptureDevice::Names());
39 NSMutableDictionary* capture_devices =
40 [[[NSMutableDictionary alloc] init] autorelease];
41 [VideoCaptureDeviceQTKit getDeviceNames:capture_devices];
42 for (NSString* key in capture_devices) {
43 VideoCaptureDevice::Name name(
44 [[[capture_devices valueForKey:key] deviceName] UTF8String],
45 [key UTF8String], VideoCaptureDevice::Name::QTKIT);
46 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
47 if (name.id().find(kBlacklistedCameras[i].name) != std::string::npos) {
48 DVLOG(2) << "Found blacklisted camera: " << name.id();
49 name.set_is_blacklisted(true);
53 device_names->push_back(name);
55 return device_names.Pass();
58 static void RunDevicesEnumeratedCallback(
59 const base::Callback<void(scoped_ptr<media::VideoCaptureDevice::Names>)>&
61 scoped_ptr<media::VideoCaptureDevice::Names> device_names) {
62 callback.Run(device_names.Pass());
66 bool VideoCaptureDeviceFactoryMac::PlatformSupportsAVFoundation() {
67 return AVFoundationGlue::IsAVFoundationSupported();
70 VideoCaptureDeviceFactoryMac::VideoCaptureDeviceFactoryMac(
71 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
72 : ui_task_runner_(ui_task_runner) {
73 thread_checker_.DetachFromThread();
76 VideoCaptureDeviceFactoryMac::~VideoCaptureDeviceFactoryMac() {}
78 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryMac::Create(
79 const VideoCaptureDevice::Name& device_name) {
80 DCHECK(thread_checker_.CalledOnValidThread());
81 DCHECK_NE(device_name.capture_api_type(),
82 VideoCaptureDevice::Name::API_TYPE_UNKNOWN);
84 // Check device presence only for AVFoundation API, since it is too expensive
85 // and brittle for QTKit. The actual initialization at device level will fail
86 // subsequently if the device is not present.
87 if (AVFoundationGlue::IsAVFoundationSupported()) {
88 scoped_ptr<VideoCaptureDevice::Names> device_names(
89 new VideoCaptureDevice::Names());
90 GetDeviceNames(device_names.get());
92 VideoCaptureDevice::Names::iterator it = device_names->begin();
93 for (; it != device_names->end(); ++it) {
94 if (it->id() == device_name.id())
97 if (it == device_names->end())
98 return scoped_ptr<VideoCaptureDevice>();
101 scoped_ptr<VideoCaptureDeviceMac> capture_device(
102 new VideoCaptureDeviceMac(device_name));
103 if (!capture_device->Init(device_name.capture_api_type())) {
104 LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
105 capture_device.reset();
107 return scoped_ptr<VideoCaptureDevice>(capture_device.Pass());
110 void VideoCaptureDeviceFactoryMac::GetDeviceNames(
111 VideoCaptureDevice::Names* device_names) {
112 DCHECK(thread_checker_.CalledOnValidThread());
113 // Loop through all available devices and add to |device_names|.
114 NSDictionary* capture_devices;
115 if (AVFoundationGlue::IsAVFoundationSupported()) {
116 bool is_any_device_blacklisted = false;
117 DVLOG(1) << "Enumerating video capture devices using AVFoundation";
118 capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
119 // Enumerate all devices found by AVFoundation, translate the info for each
120 // to class Name and add it to |device_names|.
121 for (NSString* key in capture_devices) {
122 int transport_type = [[capture_devices valueForKey:key] transportType];
123 // Transport types are defined for Audio devices and reused for video.
124 VideoCaptureDevice::Name::TransportType device_transport_type =
125 (transport_type == kIOAudioDeviceTransportTypeBuiltIn ||
126 transport_type == kIOAudioDeviceTransportTypeUSB)
127 ? VideoCaptureDevice::Name::USB_OR_BUILT_IN
128 : VideoCaptureDevice::Name::OTHER_TRANSPORT;
129 VideoCaptureDevice::Name name(
130 [[[capture_devices valueForKey:key] deviceName] UTF8String],
131 [key UTF8String], VideoCaptureDevice::Name::AVFOUNDATION,
132 device_transport_type);
133 device_names->push_back(name);
134 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
135 is_any_device_blacklisted |= EndsWith(name.id(),
136 kBlacklistedCameras[i].unique_id_signature, false);
137 if (is_any_device_blacklisted)
141 // If there is any device blacklisted in the system, walk the QTKit device
142 // list and add those devices with a blacklisted name to the |device_names|.
143 // AVFoundation and QTKit device lists partially overlap, so add a "QTKit"
144 // prefix to the latter ones to distinguish them from the AVFoundation ones.
145 if (is_any_device_blacklisted) {
146 capture_devices = [VideoCaptureDeviceQTKit deviceNames];
147 for (NSString* key in capture_devices) {
148 NSString* device_name = [[capture_devices valueForKey:key] deviceName];
149 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
150 if ([device_name rangeOfString:@(kBlacklistedCameras[i].name)
151 options:NSCaseInsensitiveSearch].length != 0) {
152 DVLOG(1) << "Enumerated blacklisted " << [device_name UTF8String];
153 VideoCaptureDevice::Name name(
154 "QTKit " + std::string([device_name UTF8String]),
155 [key UTF8String], VideoCaptureDevice::Name::QTKIT);
156 device_names->push_back(name);
162 // Also retrieve Blackmagic devices, if present, via DeckLink SDK API.
163 VideoCaptureDeviceDeckLinkMac::EnumerateDevices(device_names);
165 // We should not enumerate QTKit devices in Device Thread;
170 void VideoCaptureDeviceFactoryMac::EnumerateDeviceNames(const base::Callback<
171 void(scoped_ptr<media::VideoCaptureDevice::Names>)>& callback) {
172 DCHECK(thread_checker_.CalledOnValidThread());
173 if (AVFoundationGlue::IsAVFoundationSupported()) {
174 scoped_ptr<VideoCaptureDevice::Names> device_names(
175 new VideoCaptureDevice::Names());
176 GetDeviceNames(device_names.get());
177 callback.Run(device_names.Pass());
179 DVLOG(1) << "Enumerating video capture devices using QTKit";
180 base::PostTaskAndReplyWithResult(
181 ui_task_runner_.get(),
183 base::Bind(&EnumerateDevicesUsingQTKit),
184 base::Bind(&RunDevicesEnumeratedCallback, callback));
188 void VideoCaptureDeviceFactoryMac::GetDeviceSupportedFormats(
189 const VideoCaptureDevice::Name& device,
190 VideoCaptureFormats* supported_formats) {
191 DCHECK(thread_checker_.CalledOnValidThread());
192 switch (device.capture_api_type()) {
193 case VideoCaptureDevice::Name::AVFOUNDATION:
194 DVLOG(1) << "Enumerating video capture capabilities, AVFoundation";
195 [VideoCaptureDeviceAVFoundation getDevice:device
196 supportedFormats:supported_formats];
198 case VideoCaptureDevice::Name::QTKIT:
199 // Blacklisted cameras provide their own supported format(s), otherwise no
200 // such information is provided for QTKit.
201 if (device.is_blacklisted()) {
202 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
203 if (device.id().find(kBlacklistedCameras[i].name) !=
205 supported_formats->push_back(media::VideoCaptureFormat(
206 gfx::Size(kBlacklistedCameras[i].capture_width,
207 kBlacklistedCameras[i].capture_height),
208 kBlacklistedCameras[i].capture_frame_rate,
209 media::PIXEL_FORMAT_UYVY));
215 case VideoCaptureDevice::Name::DECKLINK:
216 DVLOG(1) << "Enumerating video capture capabilities " << device.name();
217 VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities(
218 device, supported_formats);