diff options
Diffstat (limited to 'media/video')
-rw-r--r-- | media/video/capture/mac/video_capture_device_mac.mm | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/media/video/capture/mac/video_capture_device_mac.mm b/media/video/capture/mac/video_capture_device_mac.mm index fd0a68d..fb47719 100644 --- a/media/video/capture/mac/video_capture_device_mac.mm +++ b/media/video/capture/mac/video_capture_device_mac.mm @@ -4,10 +4,17 @@ #include "media/video/capture/mac/video_capture_device_mac.h" +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/usb/USBSpec.h> + #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/mac/scoped_ioobject.h" +#include "base/mac/scoped_ioplugininterface.h" +#include "base/strings/string_number_conversions.h" #include "base/time/time.h" #import "media/video/capture/mac/avfoundation_glue.h" #import "media/video/capture/mac/platform_video_capturing_mac.h" @@ -41,6 +48,31 @@ const struct Resolution* const kWellSupportedResolutions[] = { // aspect ratio is tolerable. const float kMaxPixelAspectRatio = 1.15; +// The following constants are extracted from the specification "Universal +// Serial Bus Device Class Definition for Video Devices", Rev. 1.1 June 1, 2005. +// http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip +// CS_INTERFACE: Sec. A.4 "Video Class-Specific Descriptor Types". +const int kVcCsInterface = 0x24; +// VC_PROCESSING_UNIT: Sec. A.5 "Video Class-Specific VC Interface Descriptor +// Subtypes". +const int kVcProcessingUnit = 0x5; +// SET_CUR: Sec. A.8 "Video Class-Specific Request Codes". +const int kVcRequestCodeSetCur = 0x1; +// PU_POWER_LINE_FREQUENCY_CONTROL: Sec. A.9.5 "Processing Unit Control +// Selectors". +const int kPuPowerLineFrequencyControl = 0x5; +// Sec. 4.2.2.3.5 Power Line Frequency Control. +const int k50Hz = 1; +const int k60Hz = 2; +const int kPuPowerLineFrequencyControlCommandSize = 1; + +// Addition to the IOUSB family of structures, with subtype and unit ID. +typedef struct IOUSBInterfaceDescriptor { + IOUSBDescriptorHeader header; + UInt8 bDescriptorSubType; + UInt8 bUnitID; +} IOUSBInterfaceDescriptor; + // TODO(ronghuawu): Replace this with CapabilityList::GetBestMatchedCapability. void GetBestMatchSupportedResolution(int* width, int* height) { int min_diff = kint32max; @@ -61,6 +93,219 @@ void GetBestMatchSupportedResolution(int* width, int* height) { *height = matched_height; } +// Tries to create a user-side device interface for a given USB device. Returns +// true if interface was found and passes it back in |device_interface|. The +// caller should release |device_interface|. +static bool FindDeviceInterfaceInUsbDevice( + const int vendor_id, + const int product_id, + const io_service_t usb_device, + IOUSBDeviceInterface*** device_interface) { + // Create a plug-in, i.e. a user-side controller to manipulate USB device. + IOCFPlugInInterface** plugin; + SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService. + kern_return_t kr = + IOCreatePlugInInterfaceForService(usb_device, + kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugin, + &score); + if (kr != kIOReturnSuccess || !plugin) { + DLOG(ERROR) << "IOCreatePlugInInterfaceForService"; + return false; + } + base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin); + + // Fetch the Device Interface from the plug-in. + HRESULT res = + (*plugin)->QueryInterface(plugin, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), + reinterpret_cast<LPVOID*>(device_interface)); + if (!SUCCEEDED(res) || !*device_interface) { + DLOG(ERROR) << "QueryInterface, couldn't create interface to USB"; + return false; + } + return true; +} + +// Tries to find a Video Control type interface inside a general USB device +// interface |device_interface|, and returns it in |video_control_interface| if +// found. The returned interface must be released in the caller. +static bool FindVideoControlInterfaceInDeviceInterface( + IOUSBDeviceInterface** device_interface, + IOCFPlugInInterface*** video_control_interface) { + // Create an iterator to the list of Video-AVControl interfaces of the device, + // then get the first interface in the list. + io_iterator_t interface_iterator; + IOUSBFindInterfaceRequest interface_request = { + .bInterfaceClass = kUSBVideoInterfaceClass, + .bInterfaceSubClass = kUSBVideoControlSubClass, + .bInterfaceProtocol = kIOUSBFindInterfaceDontCare, + .bAlternateSetting = kIOUSBFindInterfaceDontCare + }; + kern_return_t kr = + (*device_interface)->CreateInterfaceIterator(device_interface, + &interface_request, + &interface_iterator); + if (kr != kIOReturnSuccess) { + DLOG(ERROR) << "Could not create an iterator to the device's interfaces."; + return false; + } + base::mac::ScopedIOObject<io_iterator_t> iterator_ref(interface_iterator); + + // There should be just one interface matching the class-subclass desired. + io_service_t found_interface; + found_interface = IOIteratorNext(interface_iterator); + if (!found_interface) { + DLOG(ERROR) << "Could not find a Video-AVControl interface in the device."; + return false; + } + base::mac::ScopedIOObject<io_service_t> found_interface_ref(found_interface); + + // Create a user side controller (i.e. a "plug-in") for the found interface. + SInt32 score; + kr = IOCreatePlugInInterfaceForService(found_interface, + kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, + video_control_interface, + &score); + if (kr != kIOReturnSuccess || !*video_control_interface) { + DLOG(ERROR) << "IOCreatePlugInInterfaceForService"; + return false; + } + return true; +} + +// Creates a control interface for |plugin_interface| and produces a command to +// set the appropriate Power Line frequency for flicker removal. +static void SetAntiFlickerInVideoControlInterface( + IOCFPlugInInterface** plugin_interface, + const int frequency) { + // Create, the control interface for the found plug-in, and release + // the intermediate plug-in. + IOUSBInterfaceInterface** control_interface = NULL; + HRESULT res = (*plugin_interface)->QueryInterface( + plugin_interface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), + reinterpret_cast<LPVOID*>(&control_interface)); + if (!SUCCEEDED(res) || !control_interface ) { + DLOG(ERROR) << "Couldn’t create control interface"; + return; + } + base::mac::ScopedIOPluginInterface<IOUSBInterfaceInterface> + control_interface_ref(control_interface); + + // Find the device's unit ID presenting type 0x24 (kVcCsInterface) and + // subtype 0x5 (kVcProcessingUnit). Inside this unit is where we find the + // power line frequency removal setting, and this id is device dependent. + int real_unit_id = -1; + IOUSBDescriptorHeader* descriptor = NULL; + IOUSBInterfaceDescriptor* cs_descriptor = NULL; + IOUSBInterfaceInterface220** interface = + reinterpret_cast<IOUSBInterfaceInterface220**>(control_interface); + while ((descriptor = (*interface)->FindNextAssociatedDescriptor( + interface, descriptor, kUSBAnyDesc))) { + cs_descriptor = + reinterpret_cast<IOUSBInterfaceDescriptor*>(descriptor); + if ((descriptor->bDescriptorType == kVcCsInterface) && + (cs_descriptor->bDescriptorSubType == kVcProcessingUnit)) { + real_unit_id = cs_descriptor->bUnitID; + break; + } + } + DVLOG_IF(1, real_unit_id == -1) << "This USB device doesn't seem to have a " + << " VC_PROCESSING_UNIT, anti-flicker not available"; + if (real_unit_id == -1) + return; + + if ((*control_interface)->USBInterfaceOpen(control_interface) != + kIOReturnSuccess) { + DLOG(ERROR) << "Unable to open control interface"; + return; + } + + // Create the control request and launch it to the device's control interface. + // Note how the wIndex needs the interface number OR'ed in the lowest bits. + IOUSBDevRequest command; + command.bmRequestType = USBmakebmRequestType(kUSBOut, + kUSBClass, + kUSBInterface); + command.bRequest = kVcRequestCodeSetCur; + UInt8 interface_number; + (*control_interface)->GetInterfaceNumber(control_interface, + &interface_number); + command.wIndex = (real_unit_id << 8) | interface_number; + const int selector = kPuPowerLineFrequencyControl; + command.wValue = (selector << 8); + command.wLength = kPuPowerLineFrequencyControlCommandSize; + command.wLenDone = 0; + int power_line_flag_value = (frequency == 50) ? k50Hz : k60Hz; + command.pData = &power_line_flag_value; + + IOReturn ret = (*control_interface)->ControlRequest(control_interface, + 0, &command); + DLOG_IF(ERROR, ret != kIOReturnSuccess) << "Anti-flicker control request" + << " failed (0x" << std::hex << ret << "), unit id: " << real_unit_id; + DVLOG_IF(1, ret == kIOReturnSuccess) << "Anti-flicker set to " << frequency + << "Hz"; + + (*control_interface)->USBInterfaceClose(control_interface); +} + +// Sets the flicker removal in a USB webcam identified by |vendor_id| and +// |product_id|, if available. The process includes first finding all USB +// devices matching the specified |vendor_id| and |product_id|; for each +// matching device, a device interface, and inside it a video control interface +// are created. The latter is used to a send a power frequency setting command. +static void SetAntiFlickerInUsbDevice(const int vendor_id, + const int product_id, + const int frequency) { + if (frequency == 0) + return; + DVLOG(1) << "Setting Power Line Frequency to " << frequency << " Hz, device " + << std::hex << vendor_id << "-" << product_id; + + // Compose a search dictionary with vendor and product ID. + CFMutableDictionaryRef query_dictionary = + IOServiceMatching(kIOUSBDeviceClassName); + CFDictionarySetValue(query_dictionary, CFSTR(kUSBVendorName), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor_id)); + CFDictionarySetValue(query_dictionary, CFSTR(kUSBProductName), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product_id)); + + io_iterator_t usb_iterator; + kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, + query_dictionary, + &usb_iterator); + if (kr != kIOReturnSuccess) { + DLOG(ERROR) << "No devices found with specified Vendor and Product ID."; + return; + } + base::mac::ScopedIOObject<io_iterator_t> usb_iterator_ref(usb_iterator); + + while (io_service_t usb_device = IOIteratorNext(usb_iterator)) { + base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device); + + IOUSBDeviceInterface** device_interface = NULL; + if (!FindDeviceInterfaceInUsbDevice(vendor_id, product_id, + usb_device, &device_interface)) { + return; + } + base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface> + device_interface_ref(device_interface); + + IOCFPlugInInterface** video_control_interface = NULL; + if (!FindVideoControlInterfaceInDeviceInterface(device_interface, + &video_control_interface)) { + return; + } + base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> + plugin_interface_ref(video_control_interface); + + SetAntiFlickerInVideoControlInterface(video_control_interface, frequency); + } +} + // TODO(mcasas): Remove the following static methods when they are no longer // referenced from VideoCaptureDeviceFactory, i.e. when all OS platforms have // splitted the VideoCaptureDevice into VideoCaptureDevice and @@ -163,6 +408,22 @@ void VideoCaptureDeviceMac::AllocateAndStart( if (!UpdateCaptureResolution()) return; } + + // Try setting the power line frequency removal (anti-flicker). The built-in + // cameras are normally suspended so the configuration must happen right + // before starting capture and during configuration. + const std::string& device_model = device_name_.GetModel(); + if (device_model.length() > 2 * kVidPidSize) { + std::string vendor_id = device_model.substr(0, kVidPidSize); + std::string model_id = device_model.substr(kVidPidSize + 1); + int vendor_id_as_int, model_id_as_int; + if (base::HexStringToInt(base::StringPiece(vendor_id), &vendor_id_as_int) && + base::HexStringToInt(base::StringPiece(model_id), &model_id_as_int)) { + SetAntiFlickerInUsbDevice(vendor_id_as_int, model_id_as_int, + GetPowerLineFrequencyForLocation()); + } + } + if (![capture_device_ startCapture]) { SetErrorState("Could not start capture device."); return; |