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