| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/mac/uvc_control_mac.h" |
| |
| #include <IOKit/IOCFPlugIn.h> |
| |
| #include "base/apple/bridging.h" |
| #include "base/apple/foundation_util.h" |
| #include "base/apple/scoped_cftyperef.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/mac/mac_util.h" |
| #include "base/mac/scoped_ioobject.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "media/capture/video/video_capture_device.h" |
| |
| namespace media { |
| |
| namespace { |
| const unsigned int kRequestTimeoutInMilliseconds = 1000; |
| |
| BASE_FEATURE(kExposeAllUvcControls, |
| "ExposeAllUvcControls", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| struct PanTilt { |
| int32_t pan; |
| int32_t tilt; |
| }; |
| |
| // Print the pan-tilt values. Used for friendly log messages. |
| ::std::ostream& operator<<(::std::ostream& os, const PanTilt& pan_tilt) { |
| return os << " pan: " << pan_tilt.pan << ", tilt " << pan_tilt.tilt; |
| } |
| |
| // Update the control range and current value of pan-tilt control if |
| // possible. |
| static void MaybeUpdatePanTiltControlRange(UvcControl& uvc, |
| mojom::Range* pan_range, |
| mojom::Range* tilt_range) { |
| PanTilt pan_tilt_max, pan_tilt_min, pan_tilt_step, pan_tilt_current; |
| if (!uvc.GetControlMax<PanTilt>(uvc::kCtPanTiltAbsoluteControl, &pan_tilt_max, |
| "pan-tilt") || |
| !uvc.GetControlMin<PanTilt>(uvc::kCtPanTiltAbsoluteControl, &pan_tilt_min, |
| "pan-tilt") || |
| !uvc.GetControlStep<PanTilt>(uvc::kCtPanTiltAbsoluteControl, |
| &pan_tilt_step, "pan-tilt") || |
| !uvc.GetControlCurrent<PanTilt>(uvc::kCtPanTiltAbsoluteControl, |
| &pan_tilt_current, "pan-tilt")) { |
| return; |
| } |
| pan_range->max = pan_tilt_max.pan; |
| pan_range->min = pan_tilt_min.pan; |
| pan_range->step = pan_tilt_step.pan; |
| pan_range->current = pan_tilt_current.pan; |
| tilt_range->max = pan_tilt_max.tilt; |
| tilt_range->min = pan_tilt_min.tilt; |
| tilt_range->step = pan_tilt_step.tilt; |
| tilt_range->current = pan_tilt_current.tilt; |
| } |
| |
| // Set pan and tilt values for a USB camera device. |
| static void SetPanTiltCurrent(UvcControl& uvc, |
| absl::optional<int> pan, |
| absl::optional<int> tilt) { |
| DCHECK(pan.has_value() || tilt.has_value()); |
| |
| PanTilt pan_tilt_current; |
| if ((!pan.has_value() || !tilt.has_value()) && |
| !uvc.GetControlCurrent<PanTilt>(uvc::kCtPanTiltAbsoluteControl, |
| &pan_tilt_current, "pan-tilt")) { |
| return; |
| } |
| |
| PanTilt pan_tilt_data; |
| pan_tilt_data.pan = |
| CFSwapInt32HostToLittle((int32_t)pan.value_or(pan_tilt_current.pan)); |
| pan_tilt_data.tilt = |
| CFSwapInt32HostToLittle((int32_t)tilt.value_or(pan_tilt_current.tilt)); |
| |
| uvc.SetControlCurrent<PanTilt>(uvc::kCtPanTiltAbsoluteControl, pan_tilt_data, |
| "pan-tilt"); |
| } |
| |
| static bool IsNonEmptyRange(const mojom::RangePtr& range) { |
| return range->min < range->max; |
| } |
| |
| } // namespace |
| |
| // Addition to the IOUSB family of structures, with subtype and unit ID. |
| // Sec. 3.7.2 "Class-Specific VC Interface Descriptor" |
| typedef struct VcCsInterfaceDescriptor { |
| IOUSBDescriptorHeader header; |
| UInt8 bDescriptorSubType; |
| UInt8 bUnitID; |
| } VcCsInterfaceDescriptor; |
| |
| // Sec. 3.7.2.5 "Processing Unit Descriptor" |
| typedef struct ProcessingUnitDescriptor { |
| UInt8 bLength; |
| UInt8 bDescriptorType; |
| UInt8 bDescriptorSubType; |
| UInt8 bUnitID; |
| UInt8 bSourceID; |
| UInt16 wMaxMultiplier; |
| UInt8 bControlSize; // Size of the bmControls field, in bytes. |
| UInt8 bmControls[]; // A bit set to 1 indicates that the mentioned Control is |
| // supported for the video stream. |
| } __attribute__((packed)) ProcessingUnitDescriptor; |
| |
| // Sec. 3.7.2.3 "Camera Terminal Descriptor" |
| typedef struct { |
| UInt8 bLength; |
| UInt8 bDescriptorType; |
| UInt8 bDescriptorSubType; |
| UInt8 bTerminalId; |
| UInt16 wTerminalType; |
| UInt8 bAssocTerminal; |
| UInt8 iTerminal; |
| UInt16 wObjectiveFocalLengthMin; |
| UInt16 wObjectiveFocalLengthMax; |
| UInt16 wOcularFocalLength; |
| UInt8 bControlSize; // Size of the bmControls field, in bytes. |
| UInt8 bmControls[]; // A bit set to 1 indicates that the mentioned Control is |
| // supported for the video stream. |
| } __attribute__((packed)) CameraTerminalDescriptor; |
| |
| static constexpr int kPuBrightnessAbsoluteControlBitIndex = 0; |
| static constexpr int kPuContrastAbsoluteControlBitIndex = 1; |
| static constexpr int kPuSaturationAbsoluteControlBitIndex = 3; |
| static constexpr int kPuSharpnessAbsoluteControlBitIndex = 4; |
| static constexpr int kPuWhiteBalanceTemperatureControlBitIndex = 5; |
| static constexpr int kPuPowerLineFrequencyControlBitIndex = 10; |
| static constexpr int kPuWhiteBalanceTemperatureAutoControlBitIndex = 12; |
| |
| static constexpr int kCtAutoExposureModeControlBitIndex = 1; |
| static constexpr int kCtExposureTimeAbsoluteControlBitIndex = 3; |
| static constexpr int kCtFocusAbsoluteControlBitIndex = 5; |
| static constexpr int kCtZoomAbsoluteControlBitIndex = 9; |
| static constexpr int kCtPanTiltAbsoluteControlBitIndex = 11; |
| static constexpr int kCtFocusAutoControlBitIndex = 17; |
| |
| static constexpr auto kProcessingUnitControlBitIndexes = |
| base::MakeFixedFlatMap<int, size_t>( |
| {{uvc::kPuBrightnessAbsoluteControl, |
| kPuBrightnessAbsoluteControlBitIndex}, |
| {uvc::kPuContrastAbsoluteControl, kPuContrastAbsoluteControlBitIndex}, |
| {uvc::kPuSaturationAbsoluteControl, |
| kPuSaturationAbsoluteControlBitIndex}, |
| {uvc::kPuSharpnessAbsoluteControl, |
| kPuSharpnessAbsoluteControlBitIndex}, |
| {uvc::kPuWhiteBalanceTemperatureControl, |
| kPuWhiteBalanceTemperatureControlBitIndex}, |
| {uvc::kPuPowerLineFrequencyControl, |
| kPuPowerLineFrequencyControlBitIndex}, |
| {uvc::kPuWhiteBalanceTemperatureAutoControl, |
| kPuWhiteBalanceTemperatureAutoControlBitIndex}}); |
| |
| static constexpr auto kCameraTerminalControlBitIndexes = |
| base::MakeFixedFlatMap<int, size_t>({ |
| {uvc::kCtAutoExposureModeControl, kCtAutoExposureModeControlBitIndex}, |
| {uvc::kCtExposureTimeAbsoluteControl, |
| kCtExposureTimeAbsoluteControlBitIndex}, |
| {uvc::kCtFocusAbsoluteControl, kCtFocusAbsoluteControlBitIndex}, |
| {uvc::kCtZoomAbsoluteControl, kCtZoomAbsoluteControlBitIndex}, |
| {uvc::kCtPanTiltAbsoluteControl, kCtPanTiltAbsoluteControlBitIndex}, |
| {uvc::kCtFocusAutoControl, kCtFocusAutoControlBitIndex}, |
| }); |
| |
| static bool FindDeviceWithVendorAndProductIds(int vendor_id, |
| int product_id, |
| io_iterator_t* usb_iterator) { |
| // Compose a search dictionary with vendor and product ID. |
| base::ScopedCFTypeRef<CFMutableDictionaryRef> query_dictionary( |
| IOServiceMatching(kIOUSBDeviceClassName)); |
| CFDictionarySetValue(query_dictionary, CFSTR(kUSBVendorName), |
| base::apple::NSToCFPtrCast(@(vendor_id))); |
| CFDictionarySetValue(query_dictionary, CFSTR(kUSBProductName), |
| base::apple::NSToCFPtrCast(@(product_id))); |
| |
| kern_return_t kr = IOServiceGetMatchingServices( |
| kIOMasterPortDefault, query_dictionary.release(), usb_iterator); |
| if (kr != kIOReturnSuccess) { |
| VLOG(1) << "No devices found with specified Vendor and Product ID."; |
| return false; |
| } |
| return true; |
| } |
| |
| // 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|. |
| static bool FindDeviceInterfaceInUsbDevice( |
| const int vendor_id, |
| const int product_id, |
| const io_service_t usb_device, |
| IOUSBDeviceInterface*** device_interface) { |
| // Create a plugin, i.e. a user-side controller to manipulate USB device. |
| base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin; |
| SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService. |
| kern_return_t kr = IOCreatePlugInInterfaceForService( |
| usb_device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, |
| plugin.InitializeInto(), &score); |
| if (kr != kIOReturnSuccess || !plugin) { |
| VLOG(1) << "IOCreatePlugInInterfaceForService"; |
| return false; |
| } |
| |
| // Fetch the Device Interface from the plugin. |
| HRESULT res = (*plugin)->QueryInterface( |
| plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), |
| reinterpret_cast<LPVOID*>(device_interface)); |
| if (!SUCCEEDED(res) || !*device_interface) { |
| VLOG(1) << "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. |
| 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. |
| base::mac::ScopedIOObject<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.InitializeInto()); |
| if (kr != kIOReturnSuccess) { |
| VLOG(1) << "Could not create an iterator to the device's interfaces."; |
| return false; |
| } |
| |
| // There should be just one interface matching the class-subclass desired. |
| base::mac::ScopedIOObject<io_service_t> found_interface( |
| IOIteratorNext(interface_iterator)); |
| if (!found_interface) { |
| VLOG(1) << "Could not find a Video-AVControl interface in the device."; |
| return false; |
| } |
| |
| // Create a user side controller (i.e. a "plugin") for the found interface. |
| SInt32 score; |
| kr = IOCreatePlugInInterfaceForService( |
| found_interface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, |
| video_control_interface, &score); |
| if (kr != kIOReturnSuccess || !*video_control_interface) { |
| VLOG(1) << "IOCreatePlugInInterfaceForService"; |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename DescriptorType> |
| std::vector<uint8_t> ExtractControls(IOUSBDescriptorHeader* usb_descriptor) { |
| auto* descriptor = reinterpret_cast<DescriptorType>(usb_descriptor); |
| if (descriptor->bControlSize > 0) { |
| NSData* data = [[NSData alloc] initWithBytes:&descriptor->bmControls[0] |
| length:descriptor->bControlSize]; |
| const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data.bytes); |
| return std::vector<uint8_t>(bytes, bytes + data.length); |
| } |
| return std::vector<uint8_t>(); |
| } |
| |
| // Open the video class specific interface in a USB webcam identified by |
| // |device_model|. Returns interface when it is successfully opened. |
| static ScopedIOUSBInterfaceInterface OpenVideoClassSpecificControlInterface( |
| std::string device_model, |
| int descriptor_subtype, |
| int& unit_id, |
| std::vector<uint8_t>& controls) { |
| if (device_model.length() <= 2 * media::kVidPidSize) { |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| std::string vendor_id = device_model.substr(0, media::kVidPidSize); |
| std::string product_id = device_model.substr(media::kVidPidSize + 1); |
| int vendor_id_as_int, product_id_as_int; |
| if (!base::HexStringToInt(vendor_id, &vendor_id_as_int) || |
| !base::HexStringToInt(product_id, &product_id_as_int)) { |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| |
| base::mac::ScopedIOObject<io_iterator_t> usb_iterator; |
| if (!FindDeviceWithVendorAndProductIds(vendor_id_as_int, product_id_as_int, |
| usb_iterator.InitializeInto())) { |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| |
| base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> |
| video_control_interface; |
| |
| while (io_service_t usb_device = IOIteratorNext(usb_iterator)) { |
| base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device); |
| base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface> device_interface; |
| |
| if (!FindDeviceInterfaceInUsbDevice(vendor_id_as_int, product_id_as_int, |
| usb_device, |
| device_interface.InitializeInto())) { |
| continue; |
| } |
| |
| if (FindVideoControlInterfaceInDeviceInterface( |
| device_interface, video_control_interface.InitializeInto())) { |
| break; |
| } |
| } |
| |
| if (video_control_interface == nullptr) { |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| |
| // Create the control interface for the found plugin, and release |
| // the intermediate plugin. |
| ScopedIOUSBInterfaceInterface control_interface; |
| HRESULT res = |
| (*video_control_interface) |
| ->QueryInterface( |
| video_control_interface, |
| CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID220), |
| reinterpret_cast<LPVOID*>(control_interface.InitializeInto())); |
| if (!SUCCEEDED(res) || !control_interface) { |
| VLOG(1) << "Couldn’t get control interface"; |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| |
| // Find the device's unit ID presenting type kVcCsInterface and the descriptor |
| // subtype. |
| IOUSBDescriptorHeader* descriptor = nullptr; |
| while ((descriptor = (*control_interface) |
| ->FindNextAssociatedDescriptor( |
| control_interface.get(), descriptor, |
| uvc::kVcCsInterface))) { |
| auto* cs_descriptor = |
| reinterpret_cast<VcCsInterfaceDescriptor*>(descriptor); |
| if (cs_descriptor->bDescriptorSubType == descriptor_subtype) { |
| unit_id = cs_descriptor->bUnitID; |
| if (descriptor_subtype == uvc::kVcProcessingUnit) { |
| controls = ExtractControls<ProcessingUnitDescriptor*>(descriptor); |
| } else if (descriptor_subtype == uvc::kVcInputTerminal) { |
| controls = ExtractControls<CameraTerminalDescriptor*>(descriptor); |
| } |
| break; |
| } |
| } |
| |
| VLOG_IF(1, unit_id == -1) << "This USB device doesn't seem to have a " |
| << descriptor_subtype << " descriptor subtype."; |
| if (unit_id == -1) { |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| |
| IOReturn ret = (*control_interface)->USBInterfaceOpen(control_interface); |
| if (ret != kIOReturnSuccess) { |
| VLOG(1) << "Unable to open control interface"; |
| |
| // Temporary additional debug logging for crbug.com/1270335 |
| VLOG_IF(1, base::mac::IsAtLeastOS12() && ret == kIOReturnExclusiveAccess) |
| << "Camera USBInterfaceOpen failed with " |
| << "kIOReturnExclusiveAccess"; |
| return ScopedIOUSBInterfaceInterface(); |
| } |
| return control_interface; |
| } |
| |
| UvcControl::UvcControl(std::string device_model, int descriptor_subtype) |
| : descriptor_subtype_(descriptor_subtype) { |
| TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "UvcControl::CreateUvcControl", "device_model", device_model, |
| "descriptor_subtype", descriptor_subtype); |
| interface_ = OpenVideoClassSpecificControlInterface( |
| device_model, descriptor_subtype, unit_id_, controls_); |
| } |
| |
| UvcControl::~UvcControl() { |
| if (interface_) { |
| (*interface_)->USBInterfaceClose(interface_); |
| } |
| } |
| // static |
| void UvcControl::SetPowerLineFrequency( |
| const VideoCaptureDeviceDescriptor& device_descriptor, |
| const VideoCaptureParams& params) { |
| PowerLineFrequency frequency = |
| VideoCaptureDevice::GetPowerLineFrequency(params); |
| if (frequency != PowerLineFrequency::kDefault) { |
| // 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 = GetDeviceModelId( |
| device_descriptor.device_id, device_descriptor.capture_api, |
| device_descriptor.transport_type); |
| if (device_model.length() > 2 * kVidPidSize) { |
| if (UvcControl uvc(device_model, uvc::kVcProcessingUnit); uvc.Good()) { |
| int power_line_flag_value = |
| (frequency == PowerLineFrequency::k50Hz) ? uvc::k50Hz : uvc::k60Hz; |
| uvc.SetControlCurrent<uint8_t>(uvc::kPuPowerLineFrequencyControl, |
| power_line_flag_value, |
| "power line frequency"); |
| } |
| } |
| } |
| } |
| |
| // static |
| void UvcControl::GetPhotoState( |
| media::mojom::PhotoStatePtr& photo_state, |
| const VideoCaptureDeviceDescriptor& device_descriptor) { |
| const std::string device_model = GetDeviceModelId( |
| device_descriptor.device_id, device_descriptor.capture_api, |
| device_descriptor.transport_type); |
| if (UvcControl uvc(device_model, uvc::kVcInputTerminal); |
| uvc.Good() && base::FeatureList::IsEnabled(kExposeAllUvcControls)) { |
| photo_state->current_focus_mode = mojom::MeteringMode::NONE; |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kCtFocusAbsoluteControl, |
| photo_state->focus_distance.get(), |
| "focus"); |
| if (IsNonEmptyRange(photo_state->focus_distance)) { |
| photo_state->supported_focus_modes.push_back(mojom::MeteringMode::MANUAL); |
| } |
| bool current_auto_focus; |
| if (uvc.GetControlCurrent<bool>(uvc::kCtFocusAutoControl, |
| |
| ¤t_auto_focus, "focus auto")) { |
| photo_state->current_focus_mode = current_auto_focus |
| ? mojom::MeteringMode::CONTINUOUS |
| : mojom::MeteringMode::MANUAL; |
| photo_state->supported_focus_modes.push_back( |
| mojom::MeteringMode::CONTINUOUS); |
| } |
| |
| photo_state->current_exposure_mode = mojom::MeteringMode::NONE; |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kCtExposureTimeAbsoluteControl, |
| photo_state->exposure_time.get(), |
| "exposure time"); |
| if (IsNonEmptyRange(photo_state->exposure_time)) { |
| photo_state->supported_exposure_modes.push_back( |
| mojom::MeteringMode::MANUAL); |
| } |
| uint8_t current_auto_exposure; |
| if (uvc.GetControlCurrent<uint8_t>(uvc::kCtAutoExposureModeControl, |
| ¤t_auto_exposure, |
| "auto-exposure mode")) { |
| if (current_auto_exposure & uvc::kExposureManual || |
| current_auto_exposure & uvc::kExposureShutterPriority) { |
| photo_state->current_exposure_mode = mojom::MeteringMode::MANUAL; |
| } else { |
| photo_state->current_exposure_mode = mojom::MeteringMode::CONTINUOUS; |
| } |
| photo_state->supported_exposure_modes.push_back( |
| mojom::MeteringMode::CONTINUOUS); |
| } |
| } |
| if (UvcControl uvc(device_model, uvc::kVcInputTerminal); uvc.Good()) { |
| MaybeUpdatePanTiltControlRange(uvc, photo_state->pan.get(), |
| photo_state->tilt.get()); |
| |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kCtZoomAbsoluteControl, |
| photo_state->zoom.get(), "zoom"); |
| } |
| if (UvcControl uvc(device_model, uvc::kVcProcessingUnit); |
| uvc.Good() && base::FeatureList::IsEnabled(kExposeAllUvcControls)) { |
| uvc.MaybeUpdateControlRange<int16_t>(uvc::kPuBrightnessAbsoluteControl, |
| photo_state->brightness.get(), |
| "brightness"); |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kPuContrastAbsoluteControl, |
| photo_state->contrast.get(), |
| "contrast"); |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kPuSaturationAbsoluteControl, |
| photo_state->saturation.get(), |
| "saturation"); |
| uvc.MaybeUpdateControlRange<uint16_t>(uvc::kPuSharpnessAbsoluteControl, |
| photo_state->sharpness.get(), |
| "sharpness"); |
| |
| photo_state->current_white_balance_mode = mojom::MeteringMode::NONE; |
| uvc.MaybeUpdateControlRange<uint16_t>( |
| uvc::kPuWhiteBalanceTemperatureControl, |
| photo_state->color_temperature.get(), "white balance temperature"); |
| if (IsNonEmptyRange(photo_state->color_temperature)) { |
| photo_state->supported_white_balance_modes.push_back( |
| mojom::MeteringMode::MANUAL); |
| } |
| bool current_auto_white_balance; |
| if (uvc.GetControlCurrent<bool>(uvc::kPuWhiteBalanceTemperatureAutoControl, |
| ¤t_auto_white_balance, |
| "white balance temperature auto")) { |
| photo_state->current_white_balance_mode = |
| current_auto_white_balance ? mojom::MeteringMode::CONTINUOUS |
| : mojom::MeteringMode::MANUAL; |
| photo_state->supported_white_balance_modes.push_back( |
| mojom::MeteringMode::CONTINUOUS); |
| } |
| } |
| } |
| |
| // static |
| void UvcControl::SetPhotoState( |
| mojom::PhotoSettingsPtr& settings, |
| const VideoCaptureDeviceDescriptor& device_descriptor) { |
| const std::string device_model = GetDeviceModelId( |
| device_descriptor.device_id, device_descriptor.capture_api, |
| device_descriptor.transport_type); |
| if (UvcControl uvc(device_model, uvc::kVcInputTerminal); |
| uvc.Good() && base::FeatureList::IsEnabled(kExposeAllUvcControls)) { |
| if (settings->has_focus_mode && |
| (settings->focus_mode == mojom::MeteringMode::CONTINUOUS || |
| settings->focus_mode == mojom::MeteringMode::MANUAL)) { |
| int focus_auto = |
| (settings->focus_mode == mojom::MeteringMode::CONTINUOUS); |
| uvc.SetControlCurrent<bool>(uvc::kCtFocusAutoControl, focus_auto, |
| "focus auto"); |
| } |
| if (settings->has_focus_distance) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kCtFocusAbsoluteControl, |
| settings->focus_distance, "focus"); |
| } |
| if (settings->has_exposure_mode && |
| (settings->exposure_mode == mojom::MeteringMode::CONTINUOUS || |
| settings->exposure_mode == mojom::MeteringMode::MANUAL)) { |
| int auto_exposure_mode = |
| settings->exposure_mode == mojom::MeteringMode::CONTINUOUS |
| ? uvc::kExposureAperturePriority |
| : uvc::kExposureManual; |
| uvc.SetControlCurrent<uint8_t>(uvc::kCtAutoExposureModeControl, |
| auto_exposure_mode, "auto-exposure mode"); |
| } |
| if (settings->has_exposure_time) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kCtExposureTimeAbsoluteControl, |
| settings->exposure_time, "exposure time"); |
| } |
| } |
| if (UvcControl uvc(device_model, uvc::kVcInputTerminal); uvc.Good()) { |
| if (settings->has_pan || settings->has_tilt) { |
| SetPanTiltCurrent(uvc, |
| settings->has_pan ? absl::make_optional(settings->pan) |
| : absl::nullopt, |
| settings->has_tilt ? absl::make_optional(settings->tilt) |
| : absl::nullopt); |
| } |
| if (settings->has_zoom) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kCtZoomAbsoluteControl, |
| settings->zoom, "zoom"); |
| } |
| } |
| if (UvcControl uvc(device_model, uvc::kVcProcessingUnit); |
| uvc.Good() && base::FeatureList::IsEnabled(kExposeAllUvcControls)) { |
| if (settings->has_brightness) { |
| uvc.SetControlCurrent<int16_t>(uvc::kPuBrightnessAbsoluteControl, |
| settings->brightness, "brightness"); |
| } |
| if (settings->has_contrast) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kPuContrastAbsoluteControl, |
| settings->contrast, "contrast"); |
| } |
| if (settings->has_saturation) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kPuSaturationAbsoluteControl, |
| settings->saturation, "saturation"); |
| } |
| if (settings->has_sharpness) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kPuSharpnessAbsoluteControl, |
| settings->sharpness, "sharpness"); |
| } |
| if (settings->has_white_balance_mode && |
| (settings->white_balance_mode == mojom::MeteringMode::CONTINUOUS || |
| settings->white_balance_mode == mojom::MeteringMode::MANUAL)) { |
| int white_balance_temperature_auto = |
| (settings->white_balance_mode == mojom::MeteringMode::CONTINUOUS); |
| uvc.SetControlCurrent<uint8_t>(uvc::kPuWhiteBalanceTemperatureAutoControl, |
| white_balance_temperature_auto, |
| "white balance temperature auto"); |
| } |
| if (settings->has_color_temperature) { |
| uvc.SetControlCurrent<uint16_t>(uvc::kPuWhiteBalanceTemperatureControl, |
| settings->color_temperature, |
| "white balance temperature"); |
| } |
| } |
| } |
| |
| // static |
| std::string UvcControl::GetDeviceModelId( |
| const std::string& device_id, |
| VideoCaptureApi capture_api, |
| VideoCaptureTransportType transport_type) { |
| // Skip the AVFoundation's not USB nor built-in devices. |
| if (capture_api == VideoCaptureApi::MACOSX_AVFOUNDATION && |
| transport_type != VideoCaptureTransportType::APPLE_USB_OR_BUILT_IN) { |
| return ""; |
| } |
| if (capture_api == VideoCaptureApi::MACOSX_DECKLINK) { |
| return ""; |
| } |
| // Both PID and VID are 4 characters. |
| if (device_id.size() < 2 * kVidPidSize) { |
| return ""; |
| } |
| |
| // The last characters of device id is a concatenation of VID and then PID. |
| const size_t vid_location = device_id.size() - 2 * kVidPidSize; |
| std::string id_vendor = device_id.substr(vid_location, kVidPidSize); |
| const size_t pid_location = device_id.size() - kVidPidSize; |
| std::string id_product = device_id.substr(pid_location, kVidPidSize); |
| |
| return id_vendor + ":" + id_product; |
| } |
| |
| // Check if the video capture device supports pan, tilt and zoom controls. |
| // static |
| VideoCaptureControlSupport UvcControl::GetControlSupport( |
| const std::string& device_model) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), |
| "VideoCaptureDeviceApple::GetControlSupport"); |
| VideoCaptureControlSupport control_support; |
| |
| UvcControl uvc(device_model, uvc::kVcInputTerminal); |
| if (!uvc.Good()) { |
| return control_support; |
| } |
| |
| uint16_t zoom_max, zoom_min = 0; |
| if (uvc.GetControlMax<uint16_t>(uvc::kCtZoomAbsoluteControl, &zoom_max, |
| "zoom") && |
| uvc.GetControlMin<uint16_t>(uvc::kCtZoomAbsoluteControl, &zoom_min, |
| "zoom") && |
| zoom_min < zoom_max) { |
| control_support.zoom = true; |
| } |
| PanTilt pan_tilt_max, pan_tilt_min; |
| if (uvc.GetControlMax<PanTilt>(uvc::kCtPanTiltAbsoluteControl, &pan_tilt_max, |
| "pan-tilt") && |
| uvc.GetControlMin<PanTilt>(uvc::kCtPanTiltAbsoluteControl, &pan_tilt_min, |
| "pan-tilt")) { |
| if (pan_tilt_min.pan < pan_tilt_max.pan) { |
| control_support.pan = true; |
| } |
| if (pan_tilt_min.tilt < pan_tilt_max.tilt) { |
| control_support.tilt = true; |
| } |
| } |
| |
| return control_support; |
| } |
| |
| bool UvcControl::IsControlAvailable(int control_selector) const { |
| if (!controls_.size()) { |
| return false; |
| } |
| size_t bitIndex; |
| if (descriptor_subtype_ == uvc::kVcProcessingUnit) { |
| const auto* it = kProcessingUnitControlBitIndexes.find(control_selector); |
| if (it == kProcessingUnitControlBitIndexes.end()) { |
| return false; |
| } |
| bitIndex = it->second; |
| } else if (descriptor_subtype_ == uvc::kVcInputTerminal) { |
| const auto* it = kCameraTerminalControlBitIndexes.find(control_selector); |
| if (it == kCameraTerminalControlBitIndexes.end()) { |
| return false; |
| } |
| bitIndex = it->second; |
| } else { |
| return false; |
| } |
| UInt8 byteIndex = bitIndex / 8; |
| if (byteIndex > controls_.size()) { |
| return false; |
| } |
| return ((controls_[byteIndex] & (1 << bitIndex % 8)) != 0); |
| } |
| |
| // Create an empty IOUSBDevRequestTO for a USB device to either set or get |
| // controls. |
| IOUSBDevRequestTO UvcControl::CreateEmptyCommand( |
| int request_code, |
| int endpoint_direction, |
| int control_selector, |
| int control_command_size) const { |
| CHECK(interface_); |
| CHECK((endpoint_direction == kUSBIn) || (endpoint_direction == kUSBOut)); |
| UInt8 interface_number; |
| (*interface_)->GetInterfaceNumber(interface_, &interface_number); |
| IOUSBDevRequestTO command; |
| memset(&command, 0, sizeof(command)); |
| command.bmRequestType = USBmakebmRequestType( |
| endpoint_direction, UInt8{kUSBClass}, UInt8{kUSBInterface}); |
| command.bRequest = request_code; |
| command.wIndex = (unit_id_ << 8) | interface_number; |
| command.wValue = (control_selector << 8); |
| command.wLength = control_command_size; |
| command.wLenDone = 0; |
| command.noDataTimeout = kRequestTimeoutInMilliseconds; |
| command.completionTimeout = kRequestTimeoutInMilliseconds; |
| return command; |
| } |
| |
| } // namespace media |