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/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/video/capture/mac/video_capture_device_avfoundation_mac.h"
16 #include "media/video/capture/mac/video_capture_device_mac.h"
17 #import "media/video/capture/mac/video_capture_device_decklink_mac.h"
18 #import "media/video/capture/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 = EndsWith(name.id(),
39 kBlacklistedCameras[i].unique_id_signature, false);
41 DVLOG_IF(2, is_device_blacklisted) << "Blacklisted camera: " <<
42 name.name() << ", id: " << name.id();
43 return is_device_blacklisted;
46 static scoped_ptr<media::VideoCaptureDevice::Names>
47 EnumerateDevicesUsingQTKit() {
48 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
50 tracked_objects::ScopedTracker tracking_profile(
51 FROM_HERE_WITH_EXPLICIT_FUNCTION(
52 "458397 media::EnumerateDevicesUsingQTKit"));
54 scoped_ptr<VideoCaptureDevice::Names> device_names(
55 new VideoCaptureDevice::Names());
56 NSMutableDictionary* capture_devices =
57 [[[NSMutableDictionary alloc] init] autorelease];
58 [VideoCaptureDeviceQTKit getDeviceNames:capture_devices];
59 for (NSString* key in capture_devices) {
60 VideoCaptureDevice::Name name(
61 [[[capture_devices valueForKey:key] deviceName] UTF8String],
62 [key UTF8String], VideoCaptureDevice::Name::QTKIT);
63 if (IsDeviceBlacklisted(name))
64 name.set_is_blacklisted(true);
65 device_names->push_back(name);
67 return device_names.Pass();
70 static void RunDevicesEnumeratedCallback(
71 const base::Callback<void(scoped_ptr<media::VideoCaptureDevice::Names>)>&
73 scoped_ptr<media::VideoCaptureDevice::Names> device_names) {
74 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
76 tracked_objects::ScopedTracker tracking_profile(
77 FROM_HERE_WITH_EXPLICIT_FUNCTION(
78 "458397 media::RunDevicesEnumeratedCallback"));
79 callback.Run(device_names.Pass());
83 bool VideoCaptureDeviceFactoryMac::PlatformSupportsAVFoundation() {
84 return AVFoundationGlue::IsAVFoundationSupported();
87 VideoCaptureDeviceFactoryMac::VideoCaptureDeviceFactoryMac(
88 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
89 : ui_task_runner_(ui_task_runner) {
90 thread_checker_.DetachFromThread();
93 VideoCaptureDeviceFactoryMac::~VideoCaptureDeviceFactoryMac() {}
95 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryMac::Create(
96 const VideoCaptureDevice::Name& device_name) {
97 DCHECK(thread_checker_.CalledOnValidThread());
98 DCHECK_NE(device_name.capture_api_type(),
99 VideoCaptureDevice::Name::API_TYPE_UNKNOWN);
101 scoped_ptr<VideoCaptureDevice> capture_device;
102 if (device_name.capture_api_type() == VideoCaptureDevice::Name::DECKLINK) {
103 capture_device.reset(new VideoCaptureDeviceDeckLinkMac(device_name));
105 VideoCaptureDeviceMac* device = new VideoCaptureDeviceMac(device_name);
106 capture_device.reset(device);
107 if (!device->Init(device_name.capture_api_type())) {
108 LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
109 capture_device.reset();
112 return scoped_ptr<VideoCaptureDevice>(capture_device.Pass());
115 void VideoCaptureDeviceFactoryMac::GetDeviceNames(
116 VideoCaptureDevice::Names* device_names) {
117 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458397 is
119 tracked_objects::ScopedTracker tracking_profile(
120 FROM_HERE_WITH_EXPLICIT_FUNCTION(
121 "458397 VideoCaptureDeviceFactoryMac::GetDeviceNames"));
122 DCHECK(thread_checker_.CalledOnValidThread());
123 // Loop through all available devices and add to |device_names|.
124 NSDictionary* capture_devices;
125 if (AVFoundationGlue::IsAVFoundationSupported()) {
126 DVLOG(1) << "Enumerating video capture devices using AVFoundation";
127 capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
128 // Enumerate all devices found by AVFoundation, translate the info for each
129 // to class Name and add it to |device_names|.
130 for (NSString* key in capture_devices) {
131 int transport_type = [[capture_devices valueForKey:key] transportType];
132 // Transport types are defined for Audio devices and reused for video.
133 VideoCaptureDevice::Name::TransportType device_transport_type =
134 (transport_type == kIOAudioDeviceTransportTypeBuiltIn ||
135 transport_type == kIOAudioDeviceTransportTypeUSB)
136 ? VideoCaptureDevice::Name::USB_OR_BUILT_IN
137 : VideoCaptureDevice::Name::OTHER_TRANSPORT;
138 VideoCaptureDevice::Name name(
139 [[[capture_devices valueForKey:key] deviceName] UTF8String],
140 [key UTF8String], VideoCaptureDevice::Name::AVFOUNDATION,
141 device_transport_type);
142 if (IsDeviceBlacklisted(name))
144 device_names->push_back(name);
146 // Also retrieve Blackmagic devices, if present, via DeckLink SDK API.
147 VideoCaptureDeviceDeckLinkMac::EnumerateDevices(device_names);
149 // We should not enumerate QTKit devices in Device Thread;
154 void VideoCaptureDeviceFactoryMac::EnumerateDeviceNames(const base::Callback<
155 void(scoped_ptr<media::VideoCaptureDevice::Names>)>& callback) {
156 DCHECK(thread_checker_.CalledOnValidThread());
157 if (AVFoundationGlue::IsAVFoundationSupported()) {
158 scoped_ptr<VideoCaptureDevice::Names> device_names(
159 new VideoCaptureDevice::Names());
160 GetDeviceNames(device_names.get());
161 callback.Run(device_names.Pass());
163 DVLOG(1) << "Enumerating video capture devices using QTKit";
164 base::PostTaskAndReplyWithResult(ui_task_runner_.get(), FROM_HERE,
165 base::Bind(&EnumerateDevicesUsingQTKit),
166 base::Bind(&RunDevicesEnumeratedCallback, callback));
170 void VideoCaptureDeviceFactoryMac::GetDeviceSupportedFormats(
171 const VideoCaptureDevice::Name& device,
172 VideoCaptureFormats* supported_formats) {
173 DCHECK(thread_checker_.CalledOnValidThread());
174 switch (device.capture_api_type()) {
175 case VideoCaptureDevice::Name::AVFOUNDATION:
176 DVLOG(1) << "Enumerating video capture capabilities, AVFoundation";
177 [VideoCaptureDeviceAVFoundation getDevice:device
178 supportedFormats:supported_formats];
180 case VideoCaptureDevice::Name::QTKIT:
181 // Blacklisted cameras provide their own supported format(s), otherwise no
182 // such information is provided for QTKit devices.
183 if (device.is_blacklisted()) {
184 for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
185 if (EndsWith(device.id(), kBlacklistedCameras[i].unique_id_signature,
187 supported_formats->push_back(media::VideoCaptureFormat(
188 gfx::Size(kBlacklistedCameras[i].capture_width,
189 kBlacklistedCameras[i].capture_height),
190 kBlacklistedCameras[i].capture_frame_rate,
191 media::PIXEL_FORMAT_UYVY));
197 case VideoCaptureDevice::Name::DECKLINK:
198 DVLOG(1) << "Enumerating video capture capabilities " << device.name();
199 VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities(
200 device, supported_formats);
208 VideoCaptureDeviceFactory*
209 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
210 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
211 return new VideoCaptureDeviceFactoryMac(ui_task_runner);