Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: media/capture/video/mac/video_capture_device_mac.mm

Issue 2143903003: [WIP] Move media/capture to device/capture (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 #include "media/capture/video/mac/video_capture_device_mac.h"
6
7 #include <IOKit/IOCFPlugIn.h>
8 #include <IOKit/usb/IOUSBLib.h>
9 #include <IOKit/usb/USBSpec.h>
10 #include <stddef.h>
11 #include <stdint.h>
12
13 #include <limits>
14 #include <utility>
15
16 #include "base/bind.h"
17 #include "base/location.h"
18 #include "base/logging.h"
19 #include "base/mac/scoped_ioobject.h"
20 #include "base/mac/scoped_ioplugininterface.h"
21 #include "base/macros.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "base/time/time.h"
26 #import "media/base/mac/avfoundation_glue.h"
27 #include "media/base/timestamp_constants.h"
28 #import "media/capture/video/mac/video_capture_device_avfoundation_mac.h"
29 #include "ui/gfx/geometry/size.h"
30
31 @implementation DeviceNameAndTransportType
32
33 - (id)initWithName:(NSString*)deviceName transportType:(int32_t)transportType {
34 if (self = [super init]) {
35 deviceName_.reset([deviceName copy]);
36 transportType_ = transportType;
37 }
38 return self;
39 }
40
41 - (NSString*)deviceName {
42 return deviceName_;
43 }
44
45 - (int32_t)transportType {
46 return transportType_;
47 }
48
49 @end // @implementation DeviceNameAndTransportType
50
51 namespace media {
52
53 // Mac specific limits for minimum and maximum frame rate.
54 const float kMinFrameRate = 1.0f;
55 const float kMaxFrameRate = 30.0f;
56
57 // In device identifiers, the USB VID and PID are stored in 4 bytes each.
58 const size_t kVidPidSize = 4;
59
60 // The following constants are extracted from the specification "Universal
61 // Serial Bus Device Class Definition for Video Devices", Rev. 1.1 June 1, 2005.
62 // http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
63 // CS_INTERFACE: Sec. A.4 "Video Class-Specific Descriptor Types".
64 const int kVcCsInterface = 0x24;
65 // VC_PROCESSING_UNIT: Sec. A.5 "Video Class-Specific VC Interface Descriptor
66 // Subtypes".
67 const int kVcProcessingUnit = 0x5;
68 // SET_CUR: Sec. A.8 "Video Class-Specific Request Codes".
69 const int kVcRequestCodeSetCur = 0x1;
70 // PU_POWER_LINE_FREQUENCY_CONTROL: Sec. A.9.5 "Processing Unit Control
71 // Selectors".
72 const int kPuPowerLineFrequencyControl = 0x5;
73 // Sec. 4.2.2.3.5 Power Line Frequency Control.
74 const int k50Hz = 1;
75 const int k60Hz = 2;
76 const int kPuPowerLineFrequencyControlCommandSize = 1;
77
78 // Addition to the IOUSB family of structures, with subtype and unit ID.
79 typedef struct IOUSBInterfaceDescriptor {
80 IOUSBDescriptorHeader header;
81 UInt8 bDescriptorSubType;
82 UInt8 bUnitID;
83 } IOUSBInterfaceDescriptor;
84
85 // Tries to create a user-side device interface for a given USB device. Returns
86 // true if interface was found and passes it back in |device_interface|. The
87 // caller should release |device_interface|.
88 static bool FindDeviceInterfaceInUsbDevice(
89 const int vendor_id,
90 const int product_id,
91 const io_service_t usb_device,
92 IOUSBDeviceInterface*** device_interface) {
93 // Create a plugin, i.e. a user-side controller to manipulate USB device.
94 IOCFPlugInInterface** plugin;
95 SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService.
96 kern_return_t kr = IOCreatePlugInInterfaceForService(
97 usb_device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
98 &score);
99 if (kr != kIOReturnSuccess || !plugin) {
100 DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
101 return false;
102 }
103 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin);
104
105 // Fetch the Device Interface from the plugin.
106 HRESULT res = (*plugin)->QueryInterface(
107 plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
108 reinterpret_cast<LPVOID*>(device_interface));
109 if (!SUCCEEDED(res) || !*device_interface) {
110 DLOG(ERROR) << "QueryInterface, couldn't create interface to USB";
111 return false;
112 }
113 return true;
114 }
115
116 // Tries to find a Video Control type interface inside a general USB device
117 // interface |device_interface|, and returns it in |video_control_interface| if
118 // found. The returned interface must be released in the caller.
119 static bool FindVideoControlInterfaceInDeviceInterface(
120 IOUSBDeviceInterface** device_interface,
121 IOCFPlugInInterface*** video_control_interface) {
122 // Create an iterator to the list of Video-AVControl interfaces of the device,
123 // then get the first interface in the list.
124 io_iterator_t interface_iterator;
125 IOUSBFindInterfaceRequest interface_request = {
126 .bInterfaceClass = kUSBVideoInterfaceClass,
127 .bInterfaceSubClass = kUSBVideoControlSubClass,
128 .bInterfaceProtocol = kIOUSBFindInterfaceDontCare,
129 .bAlternateSetting = kIOUSBFindInterfaceDontCare};
130 kern_return_t kr =
131 (*device_interface)
132 ->CreateInterfaceIterator(device_interface, &interface_request,
133 &interface_iterator);
134 if (kr != kIOReturnSuccess) {
135 DLOG(ERROR) << "Could not create an iterator to the device's interfaces.";
136 return false;
137 }
138 base::mac::ScopedIOObject<io_iterator_t> iterator_ref(interface_iterator);
139
140 // There should be just one interface matching the class-subclass desired.
141 io_service_t found_interface;
142 found_interface = IOIteratorNext(interface_iterator);
143 if (!found_interface) {
144 DLOG(ERROR) << "Could not find a Video-AVControl interface in the device.";
145 return false;
146 }
147 base::mac::ScopedIOObject<io_service_t> found_interface_ref(found_interface);
148
149 // Create a user side controller (i.e. a "plugin") for the found interface.
150 SInt32 score;
151 kr = IOCreatePlugInInterfaceForService(
152 found_interface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID,
153 video_control_interface, &score);
154 if (kr != kIOReturnSuccess || !*video_control_interface) {
155 DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
156 return false;
157 }
158 return true;
159 }
160
161 // Creates a control interface for |plugin_interface| and produces a command to
162 // set the appropriate Power Line frequency for flicker removal.
163 static void SetAntiFlickerInVideoControlInterface(
164 IOCFPlugInInterface** plugin_interface,
165 const PowerLineFrequency frequency) {
166 // Create, the control interface for the found plugin, and release
167 // the intermediate plugin.
168 IOUSBInterfaceInterface** control_interface = NULL;
169 HRESULT res =
170 (*plugin_interface)
171 ->QueryInterface(plugin_interface,
172 CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
173 reinterpret_cast<LPVOID*>(&control_interface));
174 if (!SUCCEEDED(res) || !control_interface) {
175 DLOG(ERROR) << "Couldn’t create control interface";
176 return;
177 }
178 base::mac::ScopedIOPluginInterface<IOUSBInterfaceInterface>
179 control_interface_ref(control_interface);
180
181 // Find the device's unit ID presenting type 0x24 (kVcCsInterface) and
182 // subtype 0x5 (kVcProcessingUnit). Inside this unit is where we find the
183 // power line frequency removal setting, and this id is device dependent.
184 int real_unit_id = -1;
185 IOUSBDescriptorHeader* descriptor = NULL;
186 IOUSBInterfaceDescriptor* cs_descriptor = NULL;
187 IOUSBInterfaceInterface220** interface =
188 reinterpret_cast<IOUSBInterfaceInterface220**>(control_interface);
189 while ((descriptor = (*interface)
190 ->FindNextAssociatedDescriptor(interface, descriptor,
191 kUSBAnyDesc))) {
192 cs_descriptor = reinterpret_cast<IOUSBInterfaceDescriptor*>(descriptor);
193 if ((descriptor->bDescriptorType == kVcCsInterface) &&
194 (cs_descriptor->bDescriptorSubType == kVcProcessingUnit)) {
195 real_unit_id = cs_descriptor->bUnitID;
196 break;
197 }
198 }
199 DVLOG_IF(1, real_unit_id == -1)
200 << "This USB device doesn't seem to have a "
201 << " VC_PROCESSING_UNIT, anti-flicker not available";
202 if (real_unit_id == -1)
203 return;
204
205 if ((*control_interface)->USBInterfaceOpen(control_interface) !=
206 kIOReturnSuccess) {
207 DLOG(ERROR) << "Unable to open control interface";
208 return;
209 }
210
211 // Create the control request and launch it to the device's control interface.
212 // Note how the wIndex needs the interface number OR'ed in the lowest bits.
213 IOUSBDevRequest command;
214 command.bmRequestType =
215 USBmakebmRequestType(kUSBOut, kUSBClass, kUSBInterface);
216 command.bRequest = kVcRequestCodeSetCur;
217 UInt8 interface_number;
218 (*control_interface)
219 ->GetInterfaceNumber(control_interface, &interface_number);
220 command.wIndex = (real_unit_id << 8) | interface_number;
221 const int selector = kPuPowerLineFrequencyControl;
222 command.wValue = (selector << 8);
223 command.wLength = kPuPowerLineFrequencyControlCommandSize;
224 command.wLenDone = 0;
225 int power_line_flag_value =
226 (frequency == PowerLineFrequency::FREQUENCY_50HZ) ? k50Hz : k60Hz;
227 command.pData = &power_line_flag_value;
228
229 IOReturn ret =
230 (*control_interface)->ControlRequest(control_interface, 0, &command);
231 DLOG_IF(ERROR, ret != kIOReturnSuccess) << "Anti-flicker control request"
232 << " failed (0x" << std::hex << ret
233 << "), unit id: " << real_unit_id;
234 DVLOG_IF(1, ret == kIOReturnSuccess) << "Anti-flicker set to "
235 << static_cast<int>(frequency) << "Hz";
236
237 (*control_interface)->USBInterfaceClose(control_interface);
238 }
239
240 // Sets the flicker removal in a USB webcam identified by |vendor_id| and
241 // |product_id|, if available. The process includes first finding all USB
242 // devices matching the specified |vendor_id| and |product_id|; for each
243 // matching device, a device interface, and inside it a video control interface
244 // are created. The latter is used to a send a power frequency setting command.
245 static void SetAntiFlickerInUsbDevice(const int vendor_id,
246 const int product_id,
247 const PowerLineFrequency frequency) {
248 if (frequency == PowerLineFrequency::FREQUENCY_DEFAULT)
249 return;
250 DVLOG(1) << "Setting Power Line Frequency to " << static_cast<int>(frequency)
251 << " Hz, device " << std::hex << vendor_id << "-" << product_id;
252
253 // Compose a search dictionary with vendor and product ID.
254 CFMutableDictionaryRef query_dictionary =
255 IOServiceMatching(kIOUSBDeviceClassName);
256 CFDictionarySetValue(
257 query_dictionary, CFSTR(kUSBVendorName),
258 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor_id));
259 CFDictionarySetValue(
260 query_dictionary, CFSTR(kUSBProductName),
261 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product_id));
262
263 io_iterator_t usb_iterator;
264 kern_return_t kr = IOServiceGetMatchingServices(
265 kIOMasterPortDefault, query_dictionary, &usb_iterator);
266 if (kr != kIOReturnSuccess) {
267 DLOG(ERROR) << "No devices found with specified Vendor and Product ID.";
268 return;
269 }
270 base::mac::ScopedIOObject<io_iterator_t> usb_iterator_ref(usb_iterator);
271
272 while (io_service_t usb_device = IOIteratorNext(usb_iterator)) {
273 base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device);
274
275 IOUSBDeviceInterface** device_interface = NULL;
276 if (!FindDeviceInterfaceInUsbDevice(vendor_id, product_id, usb_device,
277 &device_interface)) {
278 return;
279 }
280 base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface>
281 device_interface_ref(device_interface);
282
283 IOCFPlugInInterface** video_control_interface = NULL;
284 if (!FindVideoControlInterfaceInDeviceInterface(device_interface,
285 &video_control_interface)) {
286 return;
287 }
288 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface>
289 plugin_interface_ref(video_control_interface);
290
291 SetAntiFlickerInVideoControlInterface(video_control_interface, frequency);
292 }
293 }
294
295 const std::string VideoCaptureDevice::Name::GetModel() const {
296 // Skip the AVFoundation's not USB nor built-in devices.
297 if (capture_api_type() == AVFOUNDATION && transport_type() != USB_OR_BUILT_IN)
298 return "";
299 if (capture_api_type() == DECKLINK)
300 return "";
301 // Both PID and VID are 4 characters.
302 if (unique_id_.size() < 2 * kVidPidSize)
303 return "";
304
305 // The last characters of device id is a concatenation of VID and then PID.
306 const size_t vid_location = unique_id_.size() - 2 * kVidPidSize;
307 std::string id_vendor = unique_id_.substr(vid_location, kVidPidSize);
308 const size_t pid_location = unique_id_.size() - kVidPidSize;
309 std::string id_product = unique_id_.substr(pid_location, kVidPidSize);
310
311 return id_vendor + ":" + id_product;
312 }
313
314 VideoCaptureDeviceMac::VideoCaptureDeviceMac(const Name& device_name)
315 : device_name_(device_name),
316 task_runner_(base::ThreadTaskRunnerHandle::Get()),
317 state_(kNotInitialized),
318 capture_device_(nil),
319 weak_factory_(this) {
320 }
321
322 VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
323 DCHECK(task_runner_->BelongsToCurrentThread());
324 }
325
326 void VideoCaptureDeviceMac::AllocateAndStart(
327 const VideoCaptureParams& params,
328 std::unique_ptr<VideoCaptureDevice::Client> client) {
329 DCHECK(task_runner_->BelongsToCurrentThread());
330 if (state_ != kIdle) {
331 return;
332 }
333
334 client_ = std::move(client);
335 if (device_name_.capture_api_type() == Name::AVFOUNDATION)
336 LogMessage("Using AVFoundation for device: " + device_name_.name());
337
338 NSString* deviceId =
339 [NSString stringWithUTF8String:device_name_.id().c_str()];
340
341 [capture_device_ setFrameReceiver:this];
342
343 if (![capture_device_ setCaptureDevice:deviceId]) {
344 SetErrorState(FROM_HERE, "Could not open capture device.");
345 return;
346 }
347
348 capture_format_.frame_size = params.requested_format.frame_size;
349 capture_format_.frame_rate =
350 std::max(kMinFrameRate,
351 std::min(params.requested_format.frame_rate, kMaxFrameRate));
352 // Leave the pixel format selection to AVFoundation. The pixel format
353 // will be passed to |ReceiveFrame|.
354 capture_format_.pixel_format = PIXEL_FORMAT_UNKNOWN;
355
356 if (!UpdateCaptureResolution())
357 return;
358
359 // Try setting the power line frequency removal (anti-flicker). The built-in
360 // cameras are normally suspended so the configuration must happen right
361 // before starting capture and during configuration.
362 const std::string& device_model = device_name_.GetModel();
363 if (device_model.length() > 2 * kVidPidSize) {
364 std::string vendor_id = device_model.substr(0, kVidPidSize);
365 std::string model_id = device_model.substr(kVidPidSize + 1);
366 int vendor_id_as_int, model_id_as_int;
367 if (base::HexStringToInt(base::StringPiece(vendor_id), &vendor_id_as_int) &&
368 base::HexStringToInt(base::StringPiece(model_id), &model_id_as_int)) {
369 SetAntiFlickerInUsbDevice(vendor_id_as_int, model_id_as_int,
370 GetPowerLineFrequency(params));
371 }
372 }
373
374 if (![capture_device_ startCapture]) {
375 SetErrorState(FROM_HERE, "Could not start capture device.");
376 return;
377 }
378
379 state_ = kCapturing;
380 }
381
382 void VideoCaptureDeviceMac::StopAndDeAllocate() {
383 DCHECK(task_runner_->BelongsToCurrentThread());
384 DCHECK(state_ == kCapturing || state_ == kError) << state_;
385
386 [capture_device_ setCaptureDevice:nil];
387 [capture_device_ setFrameReceiver:nil];
388 client_.reset();
389 state_ = kIdle;
390 }
391
392 void VideoCaptureDeviceMac::TakePhoto(TakePhotoCallback callback) {
393 DCHECK(task_runner_->BelongsToCurrentThread());
394 DCHECK(state_ == kCapturing) << state_;
395
396 if (photo_callback_) // Only one picture can be in flight at a time.
397 return;
398
399 photo_callback_.reset(new TakePhotoCallback(std::move(callback)));
400 [capture_device_ takePhoto];
401 }
402
403 bool VideoCaptureDeviceMac::Init(
404 VideoCaptureDevice::Name::CaptureApiType capture_api_type) {
405 DCHECK(task_runner_->BelongsToCurrentThread());
406 DCHECK_EQ(state_, kNotInitialized);
407
408 if (capture_api_type != Name::AVFOUNDATION)
409 return false;
410
411 capture_device_.reset(
412 [[VideoCaptureDeviceAVFoundation alloc] initWithFrameReceiver:this]);
413
414 if (!capture_device_)
415 return false;
416
417 state_ = kIdle;
418 return true;
419 }
420
421 void VideoCaptureDeviceMac::ReceiveFrame(const uint8_t* video_frame,
422 int video_frame_length,
423 const VideoCaptureFormat& frame_format,
424 int aspect_numerator,
425 int aspect_denominator,
426 base::TimeDelta timestamp) {
427 if (capture_format_.frame_size != frame_format.frame_size) {
428 ReceiveError(FROM_HERE,
429 "Captured resolution " + frame_format.frame_size.ToString() +
430 ", and expected " + capture_format_.frame_size.ToString());
431 return;
432 }
433
434 client_->OnIncomingCapturedData(video_frame, video_frame_length, frame_format,
435 0, base::TimeTicks::Now(), timestamp);
436 }
437
438 void VideoCaptureDeviceMac::OnPhotoTaken(const uint8_t* image_data,
439 size_t image_length,
440 const std::string& mime_type) {
441 DCHECK(photo_callback_);
442 if (!image_data || !image_length) {
443 OnPhotoError();
444 return;
445 }
446
447 photo_callback_->Run(mojo::String::From(mime_type),
448 mojo::Array<uint8_t>(std::vector<uint8_t>(
449 image_data, image_data + image_length)));
450 photo_callback_.reset();
451 }
452
453 void VideoCaptureDeviceMac::OnPhotoError() {
454 DLOG(ERROR) << __FUNCTION__ << " error taking picture";
455 photo_callback_.reset();
456 }
457
458 void VideoCaptureDeviceMac::ReceiveError(
459 const tracked_objects::Location& from_here,
460 const std::string& reason) {
461 task_runner_->PostTask(
462 FROM_HERE, base::Bind(&VideoCaptureDeviceMac::SetErrorState,
463 weak_factory_.GetWeakPtr(), from_here, reason));
464 }
465
466 void VideoCaptureDeviceMac::SetErrorState(
467 const tracked_objects::Location& from_here,
468 const std::string& reason) {
469 DCHECK(task_runner_->BelongsToCurrentThread());
470 state_ = kError;
471 client_->OnError(from_here, reason);
472 }
473
474 void VideoCaptureDeviceMac::LogMessage(const std::string& message) {
475 DCHECK(task_runner_->BelongsToCurrentThread());
476 if (client_)
477 client_->OnLog(message);
478 }
479
480 bool VideoCaptureDeviceMac::UpdateCaptureResolution() {
481 if (![capture_device_ setCaptureHeight:capture_format_.frame_size.height()
482 width:capture_format_.frame_size.width()
483 frameRate:capture_format_.frame_rate]) {
484 ReceiveError(FROM_HERE, "Could not configure capture device.");
485 return false;
486 }
487 return true;
488 }
489
490 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/video/mac/video_capture_device_mac.h ('k') | media/capture/video/scoped_result_callback.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698