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 ¤t)) { 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