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/foundation_util.h"
20#include "base/mac/scoped_cftyperef.h"
21#include "base/mac/scoped_ioobject.h"
22#include "base/mac/scoped_ioplugininterface.h"
23#include "base/macros.h"
24#include "base/single_thread_task_runner.h"
25#include "base/strings/string_number_conversions.h"
26#include "base/strings/sys_string_conversions.h"
27#include "base/threading/thread_task_runner_handle.h"
28#include "base/time/time.h"
29#include "media/base/timestamp_constants.h"
30#include "media/capture/mojom/image_capture_types.h"
31#import "media/capture/video/mac/video_capture_device_avfoundation_mac.h"
32#include "media/capture/video/mac/video_capture_device_avfoundation_utils_mac.h"
33#include "ui/gfx/geometry/size.h"
34
35using ScopedIOUSBInterfaceInterface =
36    base::mac::ScopedIOPluginInterface<IOUSBInterfaceInterface220>;
37
38@implementation DeviceNameAndTransportType
39
40- (id)initWithName:(NSString*)deviceName transportType:(int32_t)transportType {
41  if (self = [super init]) {
42    _deviceName.reset([deviceName copy]);
43    _transportType = transportType;
44  }
45  return self;
46}
47
48- (NSString*)deviceName {
49  return _deviceName;
50}
51
52- (int32_t)transportType {
53  return _transportType;
54}
55
56@end  // @implementation DeviceNameAndTransportType
57
58namespace media {
59
60// Mac specific limits for minimum and maximum frame rate.
61const float kMinFrameRate = 1.0f;
62const float kMaxFrameRate = 30.0f;
63
64// In device identifiers, the USB VID and PID are stored in 4 bytes each.
65const size_t kVidPidSize = 4;
66
67// The following constants are extracted from the specification "Universal
68// Serial Bus Device Class Definition for Video Devices", Rev. 1.1 June 1, 2005.
69// http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
70// Sec. A.4 "Video Class-Specific Descriptor Types".
71const int kVcCsInterface = 0x24;  // CS_INTERFACE
72// Sec. A.5 "Video Class-Specific VC Interface Descriptor Subtypes".
73const int kVcInputTerminal = 0x2;   // VC_INPUT_TERMINAL
74const int kVcProcessingUnit = 0x5;  // VC_PROCESSING_UNIT
75// Sec. A.8 "Video Class-Specific Request Codes".
76const int kVcRequestCodeSetCur = 0x1;   // SET_CUR
77const int kVcRequestCodeGetCur = 0x81;  // GET_CUR
78const int kVcRequestCodeGetMin = 0x82;  // GET_MIN
79const int kVcRequestCodeGetMax = 0x83;  // GET_MAX
80const int kVcRequestCodeGetRes = 0x84;  // GET_RES
81// Sec. A.9.4. "Camera Terminal Control Selectors".
82const int kCtZoomAbsoluteControl = 0x0b;     // CT_ZOOM_ABSOLUTE_CONTROL
83const int kCtPanTiltAbsoluteControl = 0x0d;  // CT_PANTILT_ABSOLUTE_CONTROL
84// Sec. A.9.5 "Processing Unit Control Selectors".
85const int kPuPowerLineFrequencyControl =
86    0x5;  // PU_POWER_LINE_FREQUENCY_CONTROL
87// Sec. 4.2.2.3.5 "Power Line Frequency Control".
88const int k50Hz = 1;
89const int k60Hz = 2;
90const int kPuPowerLineFrequencyControlCommandSize = 1;
91// Sec. 4.2.2.1.11 "Zoom (Absolute) Control".
92const int kCtZoomAbsoluteControlCommandSize = 2;
93// Sec. 4.2.2.1.13 "PanTilt (Absolute) Control".
94const int kCtPanTiltAbsoluteControlCommandSize = 8;
95
96// Addition to the IOUSB family of structures, with subtype and unit ID.
97// Sec. 3.7.2 "Class-Specific VC Interface Descriptor"
98typedef struct VcCsInterfaceDescriptor {
99  IOUSBDescriptorHeader header;
100  UInt8 bDescriptorSubType;
101  UInt8 bUnitID;
102} VcCsInterfaceDescriptor;
103
104static bool FindDeviceWithVendorAndProductIds(int vendor_id,
105                                              int product_id,
106                                              io_iterator_t* usb_iterator) {
107  // Compose a search dictionary with vendor and product ID.
108  base::ScopedCFTypeRef<CFMutableDictionaryRef> query_dictionary(
109      IOServiceMatching(kIOUSBDeviceClassName));
110  CFDictionarySetValue(query_dictionary, CFSTR(kUSBVendorName),
111                       base::mac::NSToCFCast(@(vendor_id)));
112  CFDictionarySetValue(query_dictionary, CFSTR(kUSBProductName),
113                       base::mac::NSToCFCast(@(product_id)));
114
115  kern_return_t kr = IOServiceGetMatchingServices(
116      kIOMasterPortDefault, query_dictionary.release(), usb_iterator);
117  if (kr != kIOReturnSuccess) {
118    DLOG(ERROR) << "No devices found with specified Vendor and Product ID.";
119    return false;
120  }
121  return true;
122}
123
124// Tries to create a user-side device interface for a given USB device. Returns
125// true if interface was found and passes it back in |device_interface|. The
126// caller should release |device_interface|.
127static bool FindDeviceInterfaceInUsbDevice(
128    const int vendor_id,
129    const int product_id,
130    const io_service_t usb_device,
131    IOUSBDeviceInterface*** device_interface) {
132  // Create a plugin, i.e. a user-side controller to manipulate USB device.
133  IOCFPlugInInterface** plugin;
134  SInt32 score;  // Unused, but required for IOCreatePlugInInterfaceForService.
135  kern_return_t kr = IOCreatePlugInInterfaceForService(
136      usb_device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
137      &score);
138  if (kr != kIOReturnSuccess || !plugin) {
139    DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
140    return false;
141  }
142  base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin);
143
144  // Fetch the Device Interface from the plugin.
145  HRESULT res = (*plugin)->QueryInterface(
146      plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
147      reinterpret_cast<LPVOID*>(device_interface));
148  if (!SUCCEEDED(res) || !*device_interface) {
149    DLOG(ERROR) << "QueryInterface, couldn't create interface to USB";
150    return false;
151  }
152  return true;
153}
154
155// Tries to find a Video Control type interface inside a general USB device
156// interface |device_interface|, and returns it in |video_control_interface| if
157// found. The returned interface must be released in the caller.
158static bool FindVideoControlInterfaceInDeviceInterface(
159    IOUSBDeviceInterface** device_interface,
160    IOCFPlugInInterface*** video_control_interface) {
161  // Create an iterator to the list of Video-AVControl interfaces of the device,
162  // then get the first interface in the list.
163  io_iterator_t interface_iterator;
164  IOUSBFindInterfaceRequest interface_request = {
165      .bInterfaceClass = kUSBVideoInterfaceClass,
166      .bInterfaceSubClass = kUSBVideoControlSubClass,
167      .bInterfaceProtocol = kIOUSBFindInterfaceDontCare,
168      .bAlternateSetting = kIOUSBFindInterfaceDontCare};
169  kern_return_t kr =
170      (*device_interface)
171          ->CreateInterfaceIterator(device_interface, &interface_request,
172                                    &interface_iterator);
173  if (kr != kIOReturnSuccess) {
174    DLOG(ERROR) << "Could not create an iterator to the device's interfaces.";
175    return false;
176  }
177  base::mac::ScopedIOObject<io_iterator_t> iterator_ref(interface_iterator);
178
179  // There should be just one interface matching the class-subclass desired.
180  io_service_t found_interface;
181  found_interface = IOIteratorNext(interface_iterator);
182  if (!found_interface) {
183    DLOG(ERROR) << "Could not find a Video-AVControl interface in the device.";
184    return false;
185  }
186  base::mac::ScopedIOObject<io_service_t> found_interface_ref(found_interface);
187
188  // Create a user side controller (i.e. a "plugin") for the found interface.
189  SInt32 score;
190  kr = IOCreatePlugInInterfaceForService(
191      found_interface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID,
192      video_control_interface, &score);
193  if (kr != kIOReturnSuccess || !*video_control_interface) {
194    DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
195    return false;
196  }
197  return true;
198}
199
200// Creates a control interface for |plugin_interface| and produces a command to
201// set the appropriate Power Line frequency for flicker removal.
202static void SetAntiFlickerInVideoControlInterface(
203    IOCFPlugInInterface** plugin_interface,
204    const PowerLineFrequency frequency) {
205  // Create, the control interface for the found plugin, and release
206  // the intermediate plugin.
207  ScopedIOUSBInterfaceInterface control_interface;
208  HRESULT res =
209      (*plugin_interface)
210          ->QueryInterface(
211              plugin_interface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
212              reinterpret_cast<LPVOID*>(control_interface.InitializeInto()));
213  if (!SUCCEEDED(res) || !control_interface) {
214    DLOG(ERROR) << "Couldn’t create control interface";
215    return;
216  }
217
218  // Find the device's unit ID presenting type 0x24 (kVcCsInterface) and
219  // subtype 0x5 (kVcProcessingUnit). Inside this unit is where we find the
220  // power line frequency removal setting, and this id is device dependent.
221  int real_unit_id = -1;
222  IOUSBDescriptorHeader* descriptor = NULL;
223  while ((descriptor =
224              (*control_interface)
225                  ->FindNextAssociatedDescriptor(control_interface.get(),
226                                                 descriptor, kVcCsInterface))) {
227    auto* cs_descriptor =
228        reinterpret_cast<VcCsInterfaceDescriptor*>(descriptor);
229    if (cs_descriptor->bDescriptorSubType == kVcProcessingUnit) {
230      real_unit_id = cs_descriptor->bUnitID;
231      break;
232    }
233  }
234  DVLOG_IF(1, real_unit_id == -1)
235      << "This USB device doesn't seem to have a "
236      << " VC_PROCESSING_UNIT, anti-flicker not available";
237  if (real_unit_id == -1)
238    return;
239
240  if ((*control_interface)->USBInterfaceOpen(control_interface) !=
241      kIOReturnSuccess) {
242    DLOG(ERROR) << "Unable to open control interface";
243    return;
244  }
245
246  // Create the control request and launch it to the device's control interface.
247  // Note how the wIndex needs the interface number OR'ed in the lowest bits.
248  IOUSBDevRequest command;
249  command.bmRequestType =
250      USBmakebmRequestType(kUSBOut, kUSBClass, kUSBInterface);
251  command.bRequest = kVcRequestCodeSetCur;
252  UInt8 interface_number;
253  (*control_interface)
254      ->GetInterfaceNumber(control_interface, &interface_number);
255  command.wIndex = (real_unit_id << 8) | interface_number;
256  const int selector = kPuPowerLineFrequencyControl;
257  command.wValue = (selector << 8);
258  command.wLength = kPuPowerLineFrequencyControlCommandSize;
259  command.wLenDone = 0;
260  int power_line_flag_value =
261      (frequency == PowerLineFrequency::FREQUENCY_50HZ) ? k50Hz : k60Hz;
262  command.pData = &power_line_flag_value;
263
264  IOReturn ret =
265      (*control_interface)->ControlRequest(control_interface, 0, &command);
266  DLOG_IF(ERROR, ret != kIOReturnSuccess) << "Anti-flicker control request"
267                                          << " failed (0x" << std::hex << ret
268                                          << "), unit id: " << real_unit_id;
269  DVLOG_IF(1, ret == kIOReturnSuccess) << "Anti-flicker set to "
270                                       << static_cast<int>(frequency) << "Hz";
271
272  (*control_interface)->USBInterfaceClose(control_interface);
273}
274
275// Sets the flicker removal in a USB webcam identified by |vendor_id| and
276// |product_id|, if available. The process includes first finding all USB
277// devices matching the specified |vendor_id| and |product_id|; for each
278// matching device, a device interface, and inside it a video control interface
279// are created. The latter is used to a send a power frequency setting command.
280static void SetAntiFlickerInUsbDevice(const int vendor_id,
281                                      const int product_id,
282                                      const PowerLineFrequency frequency) {
283  if (frequency == PowerLineFrequency::FREQUENCY_DEFAULT)
284    return;
285  DVLOG(1) << "Setting Power Line Frequency to " << static_cast<int>(frequency)
286           << " Hz, device " << std::hex << vendor_id << "-" << product_id;
287
288  base::mac::ScopedIOObject<io_iterator_t> usb_iterator;
289  if (!FindDeviceWithVendorAndProductIds(vendor_id, product_id,
290                                         usb_iterator.InitializeInto())) {
291    return;
292  }
293
294  while (io_service_t usb_device = IOIteratorNext(usb_iterator)) {
295    base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device);
296
297    IOUSBDeviceInterface** device_interface = NULL;
298    if (!FindDeviceInterfaceInUsbDevice(vendor_id, product_id, usb_device,
299                                        &device_interface)) {
300      return;
301    }
302    base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface>
303        device_interface_ref(device_interface);
304
305    IOCFPlugInInterface** video_control_interface = NULL;
306    if (!FindVideoControlInterfaceInDeviceInterface(device_interface,
307                                                    &video_control_interface)) {
308      return;
309    }
310    base::mac::ScopedIOPluginInterface<IOCFPlugInInterface>
311        plugin_interface_ref(video_control_interface);
312
313    SetAntiFlickerInVideoControlInterface(video_control_interface, frequency);
314  }
315}
316
317// Create an empty IOUSBDevRequest for a USB device to either control or get
318// information from pan, tilt, and zoom controls.
319static IOUSBDevRequest CreateEmptyPanTiltZoomRequest(
320    IOUSBInterfaceInterface220** control_interface,
321    int unit_id,
322    int request_code,
323    int endpoint_direction,
324    int control_selector,
325    int control_command_size) {
326  DCHECK((endpoint_direction == kUSBIn) || (endpoint_direction == kUSBOut));
327  UInt8 interface_number;
328  (*control_interface)
329      ->GetInterfaceNumber(control_interface, &interface_number);
330  IOUSBDevRequest command;
331  command.bmRequestType =
332      USBmakebmRequestType(endpoint_direction, kUSBClass, kUSBInterface);
333  command.bRequest = request_code;
334  command.wIndex = (unit_id << 8) | interface_number;
335  command.wValue = (control_selector << 8);
336  command.wLength = control_command_size;
337  command.wLenDone = 0;
338  return command;
339}
340
341// Send USB request to get information about pan and tilt controls.
342// Returns true if the request is successful. To send the request, the interface
343// must be opened already.
344static bool SendPanTiltControlRequest(
345    IOUSBInterfaceInterface220** control_interface,
346    int unit_id,
347    int request_code,
348    int* pan_result,
349    int* tilt_result) {
350  IOUSBDevRequest command = CreateEmptyPanTiltZoomRequest(
351      control_interface, unit_id, request_code, kUSBIn,
352      kCtPanTiltAbsoluteControl, kCtPanTiltAbsoluteControlCommandSize);
353  int32_t data[2];
354  command.pData = &data;
355
356  IOReturn ret =
357      (*control_interface)->ControlRequest(control_interface, 0, &command);
358  DLOG_IF(ERROR, ret != kIOReturnSuccess)
359      << "Control pan tilt request"
360      << " failed (0x" << std::hex << ret << "), unit id: " << unit_id;
361  if (ret != kIOReturnSuccess)
362    return false;
363
364  *pan_result = USBToHostLong(data[0]);
365  *tilt_result = USBToHostLong(data[1]);
366  return true;
367}
368
369// Send USB request to get information about zoom control.
370// Returns true if the request is successful. To send the request, the interface
371// must be opened already.
372static bool SendZoomControlRequest(
373    IOUSBInterfaceInterface220** control_interface,
374    int unit_id,
375    int request_code,
376    int* result) {
377  IOUSBDevRequest command = CreateEmptyPanTiltZoomRequest(
378      control_interface, unit_id, request_code, kUSBIn, kCtZoomAbsoluteControl,
379      kCtZoomAbsoluteControlCommandSize);
380  uint16_t data;
381  command.pData = &data;
382
383  IOReturn ret =
384      (*control_interface)->ControlRequest(control_interface, 0, &command);
385  DLOG_IF(ERROR, ret != kIOReturnSuccess)
386      << "Control zoom request"
387      << " failed (0x" << std::hex << ret << "), unit id: " << unit_id;
388  if (ret != kIOReturnSuccess)
389    return false;
390
391  *result = USBToHostLong(data);
392  return true;
393}
394
395// Retrieves the control range and current value of pan and tilt controls.
396// The interface must be opened already.
397static void GetPanTiltControlRangeAndCurrent(
398    IOUSBInterfaceInterface220** control_interface,
399    int unit_id,
400    mojom::Range* pan_range,
401    mojom::Range* tilt_range) {
402  int pan_max, pan_min, pan_step, pan_current;
403  int tilt_max, tilt_min, tilt_step, tilt_current;
404  if (!SendPanTiltControlRequest(control_interface, unit_id,
405                                 kVcRequestCodeGetMax, &pan_max, &tilt_max) ||
406      !SendPanTiltControlRequest(control_interface, unit_id,
407                                 kVcRequestCodeGetMin, &pan_min, &tilt_min) ||
408      !SendPanTiltControlRequest(control_interface, unit_id,
409                                 kVcRequestCodeGetRes, &pan_step, &tilt_step) ||
410      !SendPanTiltControlRequest(control_interface, unit_id,
411                                 kVcRequestCodeGetCur, &pan_current,
412                                 &tilt_current)) {
413    return;
414  }
415  pan_range->max = pan_max;
416  pan_range->min = pan_min;
417  pan_range->step = pan_step;
418  pan_range->current = pan_current;
419  tilt_range->max = tilt_max;
420  tilt_range->min = tilt_min;
421  tilt_range->step = tilt_step;
422  tilt_range->current = tilt_current;
423}
424
425// Retrieves the control range and current value of a zoom control. The
426// interface must be opened already.
427static void GetZoomControlRangeAndCurrent(
428    IOUSBInterfaceInterface220** control_interface,
429    int unit_id,
430    mojom::Range* zoom_range) {
431  int max, min, step, current;
432  if (!SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetMax,
433                              &max) ||
434      !SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetMin,
435                              &min) ||
436      !SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetRes,
437                              &step) ||
438      !SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetCur,
439                              &current)) {
440    return;
441  }
442  zoom_range->max = max;
443  zoom_range->min = min;
444  zoom_range->step = step;
445  zoom_range->current = current;
446}
447
448// Set pan and tilt values for a USB camera device. The interface must be opened
449// already.
450static void SetPanTiltInUsbDevice(
451    IOUSBInterfaceInterface220** control_interface,
452    int unit_id,
453    base::Optional<int> pan,
454    base::Optional<int> tilt) {
455  if (!pan.has_value() && !tilt.has_value())
456    return;
457
458  int pan_current, tilt_current;
459  if ((!pan.has_value() || !tilt.has_value()) &&
460      !SendPanTiltControlRequest(control_interface, unit_id,
461                                 kVcRequestCodeGetCur, &pan_current,
462                                 &tilt_current)) {
463    return;
464  }
465
466  uint32_t pan_tilt_data[2];
467  pan_tilt_data[0] =
468      CFSwapInt32HostToLittle((uint32_t)pan.value_or(pan_current));
469  pan_tilt_data[1] =
470      CFSwapInt32HostToLittle((uint32_t)tilt.value_or(tilt_current));
471
472  IOUSBDevRequest command = CreateEmptyPanTiltZoomRequest(
473      control_interface, unit_id, kVcRequestCodeSetCur, kUSBOut,
474      kCtPanTiltAbsoluteControl, kCtPanTiltAbsoluteControlCommandSize);
475  command.pData = pan_tilt_data;
476
477  IOReturn ret =
478      (*control_interface)->ControlRequest(control_interface, 0, &command);
479  DLOG_IF(ERROR, ret != kIOReturnSuccess)
480      << "Control request"
481      << " failed (0x" << std::hex << ret << "), unit id: " << unit_id
482      << " pan value: " << pan.value_or(pan_current)
483      << " tilt value: " << tilt.value_or(tilt_current);
484  DVLOG_IF(1, ret == kIOReturnSuccess)
485      << "Setting pan value to " << pan.value_or(pan_current)
486      << " and tilt value to " << tilt.value_or(tilt_current);
487}
488
489// Set zoom value for a USB camera device. The interface must be opened already.
490static void SetZoomInUsbDevice(IOUSBInterfaceInterface220** control_interface,
491                               int unit_id,
492                               int zoom) {
493  IOUSBDevRequest command = CreateEmptyPanTiltZoomRequest(
494      control_interface, unit_id, kVcRequestCodeSetCur, kUSBOut,
495      kCtZoomAbsoluteControl, kCtZoomAbsoluteControlCommandSize);
496  command.pData = &zoom;
497
498  IOReturn ret =
499      (*control_interface)->ControlRequest(control_interface, 0, &command);
500  DLOG_IF(ERROR, ret != kIOReturnSuccess)
501      << "Control request"
502      << " failed (0x" << std::hex << ret << "), unit id: " << unit_id
503      << " zoom value: " << zoom;
504  DVLOG_IF(1, ret == kIOReturnSuccess) << "Setting zoom value to " << zoom;
505}
506
507// Open the pan, tilt, zoom interface in a USB webcam identified by
508// |device_model|. Returns interface when it is succcessfully opened. You have
509// to close the interface manually when you're done.
510static ScopedIOUSBInterfaceInterface OpenPanTiltZoomControlInterface(
511    std::string device_model,
512    int* unit_id) {
513  if (device_model.length() <= 2 * kVidPidSize) {
514    return ScopedIOUSBInterfaceInterface();
515  }
516  std::string vendor_id = device_model.substr(0, kVidPidSize);
517  std::string product_id = device_model.substr(kVidPidSize + 1);
518  int vendor_id_as_int, product_id_as_int;
519  if (!base::HexStringToInt(vendor_id, &vendor_id_as_int) ||
520      !base::HexStringToInt(product_id, &product_id_as_int)) {
521    return ScopedIOUSBInterfaceInterface();
522  }
523
524  base::mac::ScopedIOObject<io_iterator_t> usb_iterator;
525  if (!FindDeviceWithVendorAndProductIds(vendor_id_as_int, product_id_as_int,
526                                         usb_iterator.InitializeInto())) {
527    return ScopedIOUSBInterfaceInterface();
528  }
529
530  base::mac::ScopedIOPluginInterface<IOCFPlugInInterface>
531      video_control_interface;
532
533  while (io_service_t usb_device = IOIteratorNext(usb_iterator)) {
534    base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device);
535    base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface> device_interface;
536
537    if (!FindDeviceInterfaceInUsbDevice(vendor_id_as_int, product_id_as_int,
538                                        usb_device,
539                                        device_interface.InitializeInto())) {
540      continue;
541    }
542
543    if (FindVideoControlInterfaceInDeviceInterface(
544            device_interface, video_control_interface.InitializeInto())) {
545      break;
546    }
547  }
548
549  if (video_control_interface == nullptr) {
550    return ScopedIOUSBInterfaceInterface();
551  }
552
553  // Create the control interface for the found plugin, and release
554  // the intermediate plugin.
555  ScopedIOUSBInterfaceInterface control_interface;
556  HRESULT res =
557      (*video_control_interface)
558          ->QueryInterface(
559              video_control_interface,
560              CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID220),
561              reinterpret_cast<LPVOID*>(control_interface.InitializeInto()));
562  if (!SUCCEEDED(res) || !control_interface) {
563    DLOG(ERROR) << "Couldn’t get control interface";
564    return ScopedIOUSBInterfaceInterface();
565  }
566
567  // Find the device's unit ID presenting type 0x24 (kVcCsInterface) and
568  // subtype 0x2 (kVcInputTerminal). Inside this unit is where we find the
569  // settings for pan, tilt, and zoom, and this id is device dependent.
570  IOUSBDescriptorHeader* descriptor = nullptr;
571  while ((descriptor =
572              (*control_interface)
573                  ->FindNextAssociatedDescriptor(control_interface.get(),
574                                                 descriptor, kVcCsInterface))) {
575    auto* cs_descriptor =
576        reinterpret_cast<VcCsInterfaceDescriptor*>(descriptor);
577    if (cs_descriptor->bDescriptorSubType == kVcInputTerminal) {
578      *unit_id = cs_descriptor->bUnitID;
579      break;
580    }
581  }
582
583  DVLOG_IF(1, *unit_id == -1)
584      << "This USB device doesn't seem to have a "
585      << " VC_INPUT_TERMINAL. Pan, tilt, zoom are not available.";
586  if (*unit_id == -1)
587    return ScopedIOUSBInterfaceInterface();
588
589  if ((*control_interface)->USBInterfaceOpen(control_interface) !=
590      kIOReturnSuccess) {
591    DLOG(ERROR) << "Unable to open control interface";
592    return ScopedIOUSBInterfaceInterface();
593  }
594
595  return control_interface;
596}
597
598VideoCaptureDeviceMac::VideoCaptureDeviceMac(
599    const VideoCaptureDeviceDescriptor& device_descriptor)
600    : device_descriptor_(device_descriptor),
601      task_runner_(base::ThreadTaskRunnerHandle::Get()),
602      state_(kNotInitialized),
603      capture_device_(nil),
604      weak_factory_(this) {}
605
606VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
607  DCHECK(task_runner_->BelongsToCurrentThread());
608}
609
610void VideoCaptureDeviceMac::AllocateAndStart(
611    const VideoCaptureParams& params,
612    std::unique_ptr<VideoCaptureDevice::Client> client) {
613  DCHECK(task_runner_->BelongsToCurrentThread());
614  if (state_ != kIdle) {
615    return;
616  }
617
618  client_ = std::move(client);
619  if (device_descriptor_.capture_api == VideoCaptureApi::MACOSX_AVFOUNDATION)
620    LogMessage("Using AVFoundation for device: " +
621               device_descriptor_.display_name());
622
623  NSString* deviceId =
624      [NSString stringWithUTF8String:device_descriptor_.device_id.c_str()];
625
626  [capture_device_ setFrameReceiver:this];
627
628  NSString* errorMessage = nil;
629  if (![capture_device_ setCaptureDevice:deviceId errorMessage:&errorMessage]) {
630    SetErrorState(VideoCaptureError::kMacSetCaptureDeviceFailed, FROM_HERE,
631                  base::SysNSStringToUTF8(errorMessage));
632    return;
633  }
634
635  capture_format_.frame_size = params.requested_format.frame_size;
636  capture_format_.frame_rate =
637      std::max(kMinFrameRate,
638               std::min(params.requested_format.frame_rate, kMaxFrameRate));
639  // Leave the pixel format selection to AVFoundation. The pixel format
640  // will be passed to |ReceiveFrame|.
641  capture_format_.pixel_format = PIXEL_FORMAT_UNKNOWN;
642
643  if (!UpdateCaptureResolution())
644    return;
645
646  // Try setting the power line frequency removal (anti-flicker). The built-in
647  // cameras are normally suspended so the configuration must happen right
648  // before starting capture and during configuration.
649  const std::string device_model = GetDeviceModelId(
650      device_descriptor_.device_id, device_descriptor_.capture_api,
651      device_descriptor_.transport_type);
652  if (device_model.length() > 2 * kVidPidSize) {
653    std::string vendor_id = device_model.substr(0, kVidPidSize);
654    std::string product_id = device_model.substr(kVidPidSize + 1);
655    int vendor_id_as_int, product_id_as_int;
656    if (base::HexStringToInt(vendor_id, &vendor_id_as_int) &&
657        base::HexStringToInt(product_id, &product_id_as_int)) {
658      SetAntiFlickerInUsbDevice(vendor_id_as_int, product_id_as_int,
659                                GetPowerLineFrequency(params));
660    }
661  }
662
663  if (![capture_device_ startCapture]) {
664    SetErrorState(VideoCaptureError::kMacCouldNotStartCaptureDevice, FROM_HERE,
665                  "Could not start capture device.");
666    return;
667  }
668
669  client_->OnStarted();
670  state_ = kCapturing;
671}
672
673void VideoCaptureDeviceMac::StopAndDeAllocate() {
674  DCHECK(task_runner_->BelongsToCurrentThread());
675  DCHECK(state_ == kCapturing || state_ == kError) << state_;
676
677  NSString* errorMessage = nil;
678  if (![capture_device_ setCaptureDevice:nil errorMessage:&errorMessage])
679    LogMessage(base::SysNSStringToUTF8(errorMessage));
680
681  [capture_device_ setFrameReceiver:nil];
682  client_.reset();
683  state_ = kIdle;
684}
685
686void VideoCaptureDeviceMac::TakePhoto(TakePhotoCallback callback) {
687  DCHECK(task_runner_->BelongsToCurrentThread());
688  DCHECK(state_ == kCapturing) << state_;
689
690  if (photo_callback_)  // Only one picture can be in flight at a time.
691    return;
692
693  photo_callback_ = std::move(callback);
694  [capture_device_ takePhoto];
695}
696
697void VideoCaptureDeviceMac::GetPhotoState(GetPhotoStateCallback callback) {
698  DCHECK(task_runner_->BelongsToCurrentThread());
699
700  auto photo_state = mojo::CreateEmptyPhotoState();
701
702  photo_state->height = mojom::Range::New(
703      capture_format_.frame_size.height(), capture_format_.frame_size.height(),
704      capture_format_.frame_size.height(), 0 /* step */);
705  photo_state->width = mojom::Range::New(
706      capture_format_.frame_size.width(), capture_format_.frame_size.width(),
707      capture_format_.frame_size.width(), 0 /* step */);
708
709  const std::string device_model = GetDeviceModelId(
710      device_descriptor_.device_id, device_descriptor_.capture_api,
711      device_descriptor_.transport_type);
712  int unit_id = -1;
713  ScopedIOUSBInterfaceInterface control_interface(
714      OpenPanTiltZoomControlInterface(device_model, &unit_id));
715  if (control_interface) {
716    GetPanTiltControlRangeAndCurrent(control_interface, unit_id,
717                                     photo_state->pan.get(),
718                                     photo_state->tilt.get());
719    GetZoomControlRangeAndCurrent(control_interface, unit_id,
720                                  photo_state->zoom.get());
721    (*control_interface)->USBInterfaceClose(control_interface);
722  }
723
724  std::move(callback).Run(std::move(photo_state));
725}
726
727void VideoCaptureDeviceMac::SetPhotoOptions(mojom::PhotoSettingsPtr settings,
728                                            SetPhotoOptionsCallback callback) {
729  DCHECK(task_runner_->BelongsToCurrentThread());
730  // Drop |callback| and return if there are any unsupported |settings|.
731  // TODO(mcasas): centralise checks elsewhere, https://crbug.com/724285.
732  if ((settings->has_width &&
733       settings->width != capture_format_.frame_size.width()) ||
734      (settings->has_height &&
735       settings->height != capture_format_.frame_size.height()) ||
736      settings->has_fill_light_mode || settings->has_red_eye_reduction) {
737    return;
738  }
739
740  if (settings->has_pan || settings->has_tilt || settings->has_zoom) {
741    const std::string device_model = GetDeviceModelId(
742        device_descriptor_.device_id, device_descriptor_.capture_api,
743        device_descriptor_.transport_type);
744    int unit_id = -1;
745    ScopedIOUSBInterfaceInterface control_interface(
746        OpenPanTiltZoomControlInterface(device_model, &unit_id));
747    if (!control_interface)
748      return;
749
750    if (settings->has_pan || settings->has_tilt) {
751      SetPanTiltInUsbDevice(
752          control_interface, unit_id,
753          settings->has_pan ? base::make_optional(settings->pan)
754                            : base::nullopt,
755          settings->has_tilt ? base::make_optional(settings->tilt)
756                             : base::nullopt);
757    }
758    if (settings->has_zoom) {
759      SetZoomInUsbDevice(control_interface, unit_id, settings->zoom);
760    }
761    (*control_interface)->USBInterfaceClose(control_interface);
762  }
763
764  std::move(callback).Run(true);
765}
766
767bool VideoCaptureDeviceMac::Init(VideoCaptureApi capture_api_type) {
768  DCHECK(task_runner_->BelongsToCurrentThread());
769  DCHECK_EQ(state_, kNotInitialized);
770
771  if (capture_api_type != VideoCaptureApi::MACOSX_AVFOUNDATION)
772    return false;
773
774  capture_device_.reset([[GetVideoCaptureDeviceAVFoundationImplementationClass()
775      alloc] initWithFrameReceiver:this]);
776
777  if (!capture_device_)
778    return false;
779
780  state_ = kIdle;
781  return true;
782}
783
784void VideoCaptureDeviceMac::ReceiveFrame(const uint8_t* video_frame,
785                                         int video_frame_length,
786                                         const VideoCaptureFormat& frame_format,
787                                         const gfx::ColorSpace color_space,
788                                         int aspect_numerator,
789                                         int aspect_denominator,
790                                         base::TimeDelta timestamp) {
791  if (capture_format_.frame_size != frame_format.frame_size) {
792    ReceiveError(VideoCaptureError::kMacReceivedFrameWithUnexpectedResolution,
793                 FROM_HERE,
794                 "Captured resolution " + frame_format.frame_size.ToString() +
795                     ", and expected " + capture_format_.frame_size.ToString());
796    return;
797  }
798
799  client_->OnIncomingCapturedData(video_frame, video_frame_length, frame_format,
800                                  color_space, 0 /* clockwise_rotation */,
801                                  false /* flip_y */, base::TimeTicks::Now(),
802                                  timestamp);
803}
804
805void VideoCaptureDeviceMac::ReceiveExternalGpuMemoryBufferFrame(
806    gfx::GpuMemoryBufferHandle handle,
807    const VideoCaptureFormat& format,
808    const gfx::ColorSpace color_space,
809    base::TimeDelta timestamp) {
810  if (capture_format_.frame_size != format.frame_size) {
811    ReceiveError(VideoCaptureError::kMacReceivedFrameWithUnexpectedResolution,
812                 FROM_HERE,
813                 "Captured resolution " + format.frame_size.ToString() +
814                     ", and expected " + capture_format_.frame_size.ToString());
815    return;
816  }
817  client_->OnIncomingCapturedExternalBuffer(std::move(handle), format,
818                                            color_space, base::TimeTicks::Now(),
819                                            timestamp);
820}
821
822void VideoCaptureDeviceMac::OnPhotoTaken(const uint8_t* image_data,
823                                         size_t image_length,
824                                         const std::string& mime_type) {
825  DCHECK(photo_callback_);
826  if (!image_data || !image_length) {
827    OnPhotoError();
828    return;
829  }
830
831  mojom::BlobPtr blob = mojom::Blob::New();
832  blob->data.assign(image_data, image_data + image_length);
833  blob->mime_type = mime_type;
834  std::move(photo_callback_).Run(std::move(blob));
835}
836
837void VideoCaptureDeviceMac::OnPhotoError() {
838  DLOG(ERROR) << __func__ << " error taking picture";
839  photo_callback_.Reset();
840}
841
842void VideoCaptureDeviceMac::ReceiveError(VideoCaptureError error,
843                                         const base::Location& from_here,
844                                         const std::string& reason) {
845  task_runner_->PostTask(
846      FROM_HERE,
847      base::BindOnce(&VideoCaptureDeviceMac::SetErrorState,
848                     weak_factory_.GetWeakPtr(), error, from_here, reason));
849}
850
851void VideoCaptureDeviceMac::LogMessage(const std::string& message) {
852  DCHECK(task_runner_->BelongsToCurrentThread());
853  if (client_)
854    client_->OnLog(message);
855}
856
857// static
858std::string VideoCaptureDeviceMac::GetDeviceModelId(
859    const std::string& device_id,
860    VideoCaptureApi capture_api,
861    VideoCaptureTransportType transport_type) {
862  // Skip the AVFoundation's not USB nor built-in devices.
863  if (capture_api == VideoCaptureApi::MACOSX_AVFOUNDATION &&
864      transport_type != VideoCaptureTransportType::MACOSX_USB_OR_BUILT_IN)
865    return "";
866  if (capture_api == VideoCaptureApi::MACOSX_DECKLINK)
867    return "";
868  // Both PID and VID are 4 characters.
869  if (device_id.size() < 2 * kVidPidSize)
870    return "";
871
872  // The last characters of device id is a concatenation of VID and then PID.
873  const size_t vid_location = device_id.size() - 2 * kVidPidSize;
874  std::string id_vendor = device_id.substr(vid_location, kVidPidSize);
875  const size_t pid_location = device_id.size() - kVidPidSize;
876  std::string id_product = device_id.substr(pid_location, kVidPidSize);
877
878  return id_vendor + ":" + id_product;
879}
880
881// Check if the video capture device supports pan, tilt and zoom controls.
882// static
883VideoCaptureControlSupport VideoCaptureDeviceMac::GetControlSupport(
884    const std::string& device_model) {
885  VideoCaptureControlSupport control_support;
886
887  int unit_id = -1;
888  ScopedIOUSBInterfaceInterface control_interface(
889      OpenPanTiltZoomControlInterface(device_model, &unit_id));
890  if (!control_interface)
891    return control_support;
892
893  int zoom_max, zoom_min = 0;
894  if (SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetMax,
895                             &zoom_max) &&
896      SendZoomControlRequest(control_interface, unit_id, kVcRequestCodeGetMin,
897                             &zoom_min) &&
898      zoom_min < zoom_max) {
899    control_support.zoom = true;
900  }
901  int pan_max, pan_min = 0;
902  int tilt_max, tilt_min = 0;
903  if (SendPanTiltControlRequest(control_interface, unit_id,
904                                kVcRequestCodeGetMax, &pan_max, &tilt_max) &&
905      SendPanTiltControlRequest(control_interface, unit_id,
906                                kVcRequestCodeGetMin, &pan_min, &tilt_min)) {
907    if (pan_min < pan_max)
908      control_support.pan = true;
909    if (tilt_min < tilt_max)
910      control_support.tilt = true;
911  }
912
913  (*control_interface)->USBInterfaceClose(control_interface);
914  return control_support;
915}
916
917void VideoCaptureDeviceMac::SetErrorState(VideoCaptureError error,
918                                          const base::Location& from_here,
919                                          const std::string& reason) {
920  DCHECK(task_runner_->BelongsToCurrentThread());
921  state_ = kError;
922  client_->OnError(error, from_here, reason);
923}
924
925bool VideoCaptureDeviceMac::UpdateCaptureResolution() {
926  if (![capture_device_ setCaptureHeight:capture_format_.frame_size.height()
927                                   width:capture_format_.frame_size.width()
928                               frameRate:capture_format_.frame_rate]) {
929    ReceiveError(VideoCaptureError::kMacUpdateCaptureResolutionFailed,
930                 FROM_HERE, "Could not configure capture device.");
931    return false;
932  }
933  return true;
934}
935
936}  // namespace media
937