[cscore] Add UVC Protocol Support for USB Camera Controls on macOS (#7926)

This commit is contained in:
Yuhao
2025-04-30 22:02:59 +08:00
committed by GitHub
parent a4572a01b7
commit 8fe3cfb325
8 changed files with 1438 additions and 18 deletions

View File

@@ -46,6 +46,7 @@ objc_library(
"Foundation",
"CoreMedia",
"CoreVideo",
"IOKit",
],
tags = ["manual"],
deps = [

View File

@@ -21,7 +21,7 @@ if(APPLE)
cscore
PROPERTIES
LINK_FLAGS
"-framework CoreFoundation -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo"
"-framework CoreFoundation -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo -framework IOKit"
)
elseif(MSVC)
target_sources(cscore PRIVATE ${cscore_windows_src})

View File

@@ -12,6 +12,8 @@
#include <string>
#include <optional>
#include <wpi/StringMap.h>
#include "SourceImpl.h"
namespace cs {
@@ -88,8 +90,34 @@ class UsbCameraImpl : public SourceImpl {
UsbCameraImplObjc* cppGetObjc() { return m_objc; }
int CreatePropertyPublic(std::string_view name, std::function<std::unique_ptr<PropertyImpl>()> newFunc) {
return CreateProperty(name, newFunc);
}
PropertyImpl* GetPropertyPublic(int property) {
return GetProperty(property);
}
void NotifyPropertyCreatedPublic(int propIndex, PropertyImpl& prop) {
NotifyPropertyCreated(propIndex, prop);
}
void UpdatePropertyValuePublic(int property, bool setString, int value, std::string_view valueStr) {
UpdatePropertyValue(property, setString, value, valueStr);
}
wpi::mutex& GetMutex() { return m_mutex; }
// Property cache accessors
wpi::StringMap<uint32_t>& GetPropertyCache() { return m_propertyCache; }
wpi::StringMap<uint32_t>& GetPropertyAutoCache() { return m_propertyAutoCache; }
private:
UsbCameraImplObjc* m_objc;
std::vector<CameraModeStore> m_platformModes;
// Property caches
wpi::StringMap<uint32_t> m_propertyCache;
wpi::StringMap<uint32_t> m_propertyAutoCache;
};
} // namespace cs

View File

@@ -5,11 +5,39 @@
#pragma once
#import <AVFoundation/AVFoundation.h>
#import "UsbCameraDelegate.h"
#include <memory>
#include <string_view>
#import "UsbCameraDelegate.h"
#import "UvcControlImpl.h"
#include "cscore_cpp.h"
// Quirk: exposure auto is 3 for on, 1 for off
#define kPropertyAutoExposureOn 3
#define kPropertyAutoExposureOff 1
// Property names
#define kPropertyBrightness "brightness"
#define kPropertyWhiteBalance "white_balance_temperature"
#define kPropertyExposure "raw_exposure_time_absolute"
#define kPropertyContrast "raw_contrast"
#define kPropertySaturation "raw_saturation"
#define kPropertySharpness "raw_sharpness"
#define kPropertyGain "gain"
#define kPropertyGamma "gamma"
#define kPropertyHue "raw_hue"
#define kPropertyFocus "focus_absolute"
#define kPropertyZoom "zoom"
#define kPropertyBackLightCompensation "backlight_compensation"
#define kPropertyPowerLineFrequency "power_line_frequency"
// Auto property names
#define kPropertyAutoExposure "exposure_auto"
#define kPropertyAutoWhiteBalance "white_balance_automatic"
#define kPropertyAutoFocus "focus_auto"
namespace cs {
class UsbCameraImpl;
}
@@ -30,6 +58,7 @@ class UsbCameraImpl;
@property(nonatomic) AVCaptureDevice* videoDevice;
@property(nonatomic) AVCaptureDeviceInput* videoInput;
@property(nonatomic) UsbCameraDelegate* callback;
@property(nonatomic) UvcControlImpl* uvcControl;
@property(nonatomic) AVCaptureVideoDataOutput* videoOutput;
@property(nonatomic) AVCaptureSession* session;
@@ -68,4 +97,8 @@ class UsbCameraImpl;
- (void)getCameraName:(std::string*)name;
- (void)setNewCameraPath:(std::string_view*)path;
- (void)deviceCacheProperties;
- (void)cacheProperty:(uint32_t)propID withName:(NSString *)name;
- (void)cacheAutoProperty:(uint32_t)propID withName:(NSString *)baseName;
@end

View File

@@ -2,12 +2,14 @@
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#import "UsbCameraImplObjc.h"
#include "UsbCameraImpl.h"
#include <wpi/SmallString.h>
#pragma GCC diagnostic ignored "-Wunused-parameter"
#import "UsbCameraImplObjc.h"
#include "Notifier.h"
#include "Log.h"
#include "UsbCameraImpl.h"
template <typename S, typename... Args>
inline void NamedLog(UsbCameraImplObjc* objc, unsigned int level,
@@ -104,44 +106,300 @@ using namespace cs;
name:AVCaptureDeviceWasDisconnectedNotification
object:nil];
[self deviceConnect];
[self deviceCacheProperties];
});
}
- (BOOL)getEnabledWithProperty:(int)property withValue:(int)value {
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
return false;
}
// There is room for quirk handling improvement here, but I will leave it
// for now.
if (property == sharedThis->GetPropertyIndex(kPropertyAutoExposure)) {
return value == kPropertyAutoExposureOn;
}
return value != 0;
}
- (int)clampToPercent:(int)value {
if (value < 0) {
return 0;
}
if (value > 100) {
return 100;
}
return value;
}
- (int)percentageToRaw:(int)propID percentage:(int)percentage min:(int)min max:(int)max {
if (min == max) {
return min;
}
return min + (max - min) * percentage / 100;
}
- (BOOL)isPercentageProperty:(int)propID {
return propID == CAPPROPID_BRIGHTNESS ||
propID == CAPPROPID_CONTRAST ||
propID == CAPPROPID_SATURATION ||
propID == CAPPROPID_HUE ||
propID == CAPPROPID_SHARPNESS ||
propID == CAPPROPID_GAIN;
}
// Property functions
- (void)setProperty:(int)property
withValue:(int)value
status:(CS_Status*)status {
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// Get the property name from the property index
wpi::SmallString<128> nameBuf;
std::string_view propName = sharedThis->GetPropertyName(property, nameBuf, status);
if (*status != 0) {
OBJCERROR("Failed to get property name for index {}", property);
return;
}
std::string nameStr(propName);
// Check if it's an auto property
auto& propertyAutoCache = sharedThis->GetPropertyAutoCache();
auto autoIt = propertyAutoCache.find(nameStr);
if (autoIt != propertyAutoCache.end()) {
uint32_t propID = autoIt->second;
bool enabled = [self getEnabledWithProperty:property withValue:value];
dispatch_async_and_wait(self.sessionQueue, ^{
if (self.uvcControl == nil) {
*status = CS_INVALID_PROPERTY;
return;
}
if (![self.uvcControl setAutoProperty:propID enabled:enabled status:status]) {
OBJCERROR("Failed to set auto property {} to {}",
nameStr, enabled);
return;
}
// Update property value
sharedThis->UpdatePropertyValuePublic(property, false, value, {});
});
return;
}
// Handle regular property
auto& propertyCache = sharedThis->GetPropertyCache();
auto it = propertyCache.find(nameStr);
if (it == propertyCache.end()) {
OBJCERROR("Property not found in cache: {}", nameStr);
*status = CS_INVALID_PROPERTY;
return;
}
uint32_t propID = it->second;
dispatch_async_and_wait(self.sessionQueue, ^{
if (self.uvcControl == nil) {
*status = CS_INVALID_PROPERTY;
return;
}
// Get the property implementation to access its limits
const PropertyImpl* prop = sharedThis->GetPropertyPublic(property);
if (!prop) {
*status = CS_INVALID_PROPERTY;
return;
}
int32_t realValue = value;
if ([self isPercentageProperty:propID]) {
// Clamp to 0-100
realValue = [self clampToPercent:realValue];
// Scale to min/max
realValue = [self percentageToRaw:propID percentage:realValue min:prop->minimum max:prop->maximum];
}
if (![self.uvcControl setProperty:propID withValue:realValue status:status]) {
OBJCERROR("Failed to set property {} to value {}", nameStr, realValue);
return;
}
// Update property value in the container
sharedThis->UpdatePropertyValuePublic(property, false, value, {});
});
}
- (void)setStringProperty:(int)property
withValue:(std::string_view*)value
status:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
return;
}
// Standard common camera properties
- (void)setBrightness:(int)brightness status:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// Get the property index and set it
int prop = sharedThis->GetPropertyIndex(kPropertyBrightness);
sharedThis->SetProperty(prop, brightness, status);
}
- (int)getBrightness:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
return 0;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return 0;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// Get the property index and its value
int prop = sharedThis->GetPropertyIndex(kPropertyBrightness);
return sharedThis->GetProperty(prop, status);
}
- (void)setWhiteBalanceAuto:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
int prop = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
sharedThis->SetProperty(prop, 1, status);
}
- (void)setWhiteBalanceHoldCurrent:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
int prop = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
sharedThis->SetProperty(prop, 0, status);
}
- (void)setWhiteBalanceManual:(int)value status:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// First disable auto white balance
int autoProp = sharedThis->GetPropertyIndex(kPropertyAutoWhiteBalance);
sharedThis->SetProperty(autoProp, 0, status);
if (*status != 0) {
return;
}
// Then set the white balance value
int prop = sharedThis->GetPropertyIndex(kPropertyWhiteBalance);
sharedThis->SetProperty(prop, value, status);
}
- (void)setExposureAuto:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// Set the auto exposure property to enabled (1)
int prop = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
sharedThis->SetProperty(prop, kPropertyAutoExposureOn, status);
}
- (void)setExposureHoldCurrent:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// Set the auto exposure property to disabled (0)
int prop = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
sharedThis->SetProperty(prop, kPropertyAutoExposureOff, status);
}
- (void)setExposureManual:(int)value status:(CS_Status*)status {
*status = CS_INVALID_PROPERTY;
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
*status = CS_INVALID_HANDLE;
return;
}
// Make sure properties are cached
if (!self.propertiesCached) {
[self deviceCacheProperties];
}
// First disable auto exposure
int autoProp = sharedThis->GetPropertyIndex(kPropertyAutoExposure);
sharedThis->SetProperty(autoProp, kPropertyAutoExposureOff, status);
if (*status != 0) {
return;
}
// Then set the exposure value
int prop = sharedThis->GetPropertyIndex(kPropertyExposure);
sharedThis->SetProperty(prop, value, status);
}
- (bool)setVideoMode:(const cs::VideoMode&)mode status:(CS_Status*)status {
@@ -295,10 +553,144 @@ using namespace cs;
// All above are called from C++, must always dispatch to loop
// Property caching methods
- (void)deviceCacheProperties {
if (self.session == nil) {
return;
}
if (self.uvcControl == nil) {
return;
}
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
OBJCERROR("Cannot cache properties: UsbCameraImpl not available");
return;
}
// Cache basic properties
[self cacheProperty:CAPPROPID_BRIGHTNESS withName:@kPropertyBrightness];
[self cacheProperty:CAPPROPID_WHITEBALANCE withName:@kPropertyWhiteBalance];
[self cacheProperty:CAPPROPID_EXPOSURE withName:@kPropertyExposure];
[self cacheProperty:CAPPROPID_CONTRAST withName:@kPropertyContrast];
[self cacheProperty:CAPPROPID_SATURATION withName:@kPropertySaturation];
[self cacheProperty:CAPPROPID_SHARPNESS withName:@kPropertySharpness];
[self cacheProperty:CAPPROPID_GAIN withName:@kPropertyGain];
[self cacheProperty:CAPPROPID_GAMMA withName:@kPropertyGamma];
[self cacheProperty:CAPPROPID_HUE withName:@kPropertyHue];
[self cacheProperty:CAPPROPID_FOCUS withName:@kPropertyFocus];
[self cacheProperty:CAPPROPID_ZOOM withName:@kPropertyZoom];
[self cacheProperty:CAPPROPID_BACKLIGHTCOMP withName:@kPropertyBackLightCompensation];
[self cacheProperty:CAPPROPID_POWERLINEFREQ withName:@kPropertyPowerLineFrequency];
// Cache auto properties
[self cacheAutoProperty:CAPPROPID_EXPOSURE withName:@kPropertyAutoExposure];
[self cacheAutoProperty:CAPPROPID_WHITEBALANCE withName:@kPropertyAutoWhiteBalance];
[self cacheAutoProperty:CAPPROPID_FOCUS withName:@kPropertyAutoFocus];
self.propertiesCached = true;
}
- (void)cacheProperty:(uint32_t)propID withName:(NSString *)name {
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
OBJCERROR("Cannot cache property: UsbCameraImpl not available");
return;
}
if (self.uvcControl == nil) {
OBJCWARNING("Cannot cache property {}: UVC control not initialized", [name UTF8String]);
return;
}
// Get property limits
int32_t minimum = 0, maximum = 0, defaultValue = 0;
int32_t value = defaultValue;
CS_Status status;
std::string nameStr = std::string([name UTF8String]);
// Get the property limits
if (![self.uvcControl getPropertyLimits:propID
min:&minimum
max:&maximum
defValue:&defaultValue
status:&status]) {
OBJCWARNING("Failed to get property limits for {}", nameStr);
return;
}
// Get current value
if (![self.uvcControl getProperty:propID withValue:&value status:&status]) {
value = defaultValue;
OBJCWARNING("Failed to get current value for {}: {}",
nameStr, value);
return;
}
// Create property
auto& propertyCache = sharedThis->GetPropertyCache();
propertyCache[nameStr] = propID;
// Create the property implementation
std::unique_ptr<PropertyImpl> prop;
prop = std::make_unique<PropertyImpl>(nameStr);
prop->propKind = CS_PROP_INTEGER;
prop->value = value;
prop->minimum = minimum;
prop->maximum = maximum;
prop->step = 1; // Most camera properties use a step of 1
prop->defaultValue = defaultValue;
// Add the property to the container
std::scoped_lock lock(sharedThis->GetMutex());
int ndx = sharedThis->CreatePropertyPublic(nameStr, [&] { return std::move(prop); });
// Notify that property has been created
sharedThis->NotifyPropertyCreatedPublic(ndx, *sharedThis->GetPropertyPublic(ndx));
}
- (void)cacheAutoProperty:(uint32_t)propID withName:(NSString *)baseName {
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
OBJCERROR("Cannot cache auto property: UsbCameraImpl not available");
return;
}
if (self.uvcControl == nil) {
OBJCWARNING("Cannot cache auto property {}: UVC control not initialized", [baseName UTF8String]);
return;
}
// Build auto mode property name
std::string nameStr = std::string([baseName UTF8String]);
// Get current auto mode status
bool enabled = false;
CS_Status status = 0;
if(![self.uvcControl getAutoProperty:propID enabled:&enabled status:&status]) {
OBJCWARNING("Failed to get auto property {}", nameStr);
return;
}
// Create property
std::unique_ptr<PropertyImpl> prop;
prop = std::make_unique<PropertyImpl>(nameStr);
prop->propKind = CS_PROP_BOOLEAN;
prop->value = enabled ? 1 : 0;
prop->minimum = 0;
prop->maximum = 1;
prop->step = 1;
prop->defaultValue = 0; // Default is manual mode
// Add property to container
std::scoped_lock lock(sharedThis->GetMutex());
int ndx = sharedThis->CreatePropertyPublic(nameStr, [&] { return std::move(prop); });
// Notify property created
sharedThis->NotifyPropertyCreatedPublic(ndx, *sharedThis->GetPropertyPublic(ndx));
// Map property name to ID
auto& propertyAutoCache = sharedThis->GetPropertyAutoCache();
propertyAutoCache[nameStr] = propID;
}
static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
@@ -459,6 +851,53 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
self.deviceValid = true;
}
- (CMTime)findNearestFrameDuration:(int)fps {
if (self.currentFormat == nil) {
return CMTimeMake(1, fps);
}
NSArray<AVFrameRateRange*>* frameRates = self.currentFormat.videoSupportedFrameRateRanges;
if (frameRates.count == 0) {
return CMTimeMake(1, fps);
}
// Find the nearest frame duration
CMTime nearestDuration = CMTimeMake(1, fps);
double minDiff = DBL_MAX;
for (AVFrameRateRange* range in frameRates) {
CMTime minDuration = range.minFrameDuration;
CMTime maxDuration = range.maxFrameDuration;
// Calculate frame duration for current fps
CMTime targetDuration = CMTimeMake(1, fps);
// Check if within range
if (CMTimeCompare(targetDuration, minDuration) >= 0 &&
CMTimeCompare(targetDuration, maxDuration) <= 0) {
return targetDuration;
}
// Calculate difference with min value
double minDiffValue = fabs(CMTimeGetSeconds(targetDuration) - CMTimeGetSeconds(minDuration));
if (minDiffValue < minDiff) {
minDiff = minDiffValue;
nearestDuration = minDuration;
}
// Calculate difference with max value
double maxDiffValue = fabs(CMTimeGetSeconds(targetDuration) - CMTimeGetSeconds(maxDuration));
if (maxDiffValue < minDiff) {
minDiff = maxDiffValue;
nearestDuration = maxDuration;
}
}
OBJCDEBUG("Nearest fps: {}", nearestDuration.timescale / static_cast<double>(nearestDuration.value));
return nearestDuration;
}
- (bool)deviceStreamOn {
if (self.streaming) {
return false;
@@ -476,9 +915,9 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
}
if (self.currentFPS != 0) {
self.videoDevice.activeVideoMinFrameDuration =
CMTimeMake(1, self.currentFPS);
[self findNearestFrameDuration:self.currentFPS];
self.videoDevice.activeVideoMaxFrameDuration =
CMTimeMake(1, self.currentFPS);
[self findNearestFrameDuration:self.currentFPS];
}
[self.videoDevice unlockForConfiguration];
} else {
@@ -574,6 +1013,16 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
goto err;
}
CS_Status status;
self.uvcControl = [UvcControlImpl createFromAVCaptureDevice:self.videoDevice status:&status];
if (self.uvcControl == nil) {
OBJCWARNING("Failed to initialize UVC control for camera: {}", status);
} else {
OBJCINFO("UVC control initialized successfully");
}
self.uvcControl.cppImpl = self.cppImpl;
self.callback = [[UsbCameraDelegate alloc] init];
if (self.callback == nil) {
OBJCWARNING("Creating Camera Callback failed");

View File

@@ -0,0 +1,129 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#import <AVFoundation/AVFoundation.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/usb/IOUSBLib.h>
#include <memory>
#include <string_view>
#import "UsbCameraDelegate.h"
#include "cscore_cpp.h"
// Status code definition
#define CS_UVC_STATUS_ERROR -3001
#define CS_UVC_STATUS_DEVICE_DISCONNECTED -3002
// UVC control selector definitions
#define UVC_INPUT_TERMINAL_ID 0x01
// Camera terminal control selectors
#define CT_AE_MODE_CONTROL 0x02
#define CT_AE_PRIORITY_CONTROL 0x03
#define CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04
#define CT_EXPOSURE_TIME_RELATIVE_CONTROL 0x05
#define CT_FOCUS_ABSOLUTE_CONTROL 0x06
#define CT_FOCUS_RELATIVE_CONTROL 0x07
#define CT_FOCUS_AUTO_CONTROL 0x08
#define CT_ZOOM_ABSOLUTE_CONTROL 0x0B
#define CT_ZOOM_RELATIVE_CONTROL 0x0C
// Processing unit control selectors
#define PU_BACKLIGHT_COMPENSATION_CONTROL 0x01
#define PU_BRIGHTNESS_CONTROL 0x02
#define PU_CONTRAST_CONTROL 0x03
#define PU_GAIN_CONTROL 0x04
#define PU_POWER_LINE_FREQUENCY_CONTROL 0x05
#define PU_HUE_CONTROL 0x06
#define PU_SATURATION_CONTROL 0x07
#define PU_SHARPNESS_CONTROL 0x08
#define PU_GAMMA_CONTROL 0x09
#define PU_WHITE_BALANCE_TEMPERATURE_CONTROL 0x0A
#define PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0B
#define PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0C
#define PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0D
#define PU_HUE_AUTO_CONTROL 0x10
#define PU_CONTRAST_AUTO_CONTROL 0x13
// Camera request error code
#define VC_REQUEST_ERROR_CODE_CONTROL 0x02
// UVC control interface definitions
#define UVC_CONTROL_INTERFACE_CLASS 14
#define UVC_CONTROL_INTERFACE_SUBCLASS 1
// UVC control request types
#define UVC_SET_CUR 0x01
#define UVC_GET_CUR 0x81
#define UVC_GET_MIN 0x82
#define UVC_GET_MAX 0x83
#define UVC_GET_RES 0x84
#define UVC_GET_INFO 0x86
#define UVC_GET_DEF 0x87
// Camera property ID definitions
#define CAPPROPID_EXPOSURE 1
#define CAPPROPID_FOCUS 2
#define CAPPROPID_ZOOM 3
#define CAPPROPID_WHITEBALANCE 4
#define CAPPROPID_GAIN 5
#define CAPPROPID_BRIGHTNESS 6
#define CAPPROPID_CONTRAST 7
#define CAPPROPID_SATURATION 8
#define CAPPROPID_GAMMA 9
#define CAPPROPID_HUE 10
#define CAPPROPID_SHARPNESS 11
#define CAPPROPID_BACKLIGHTCOMP 12
#define CAPPROPID_POWERLINEFREQ 13
#define CAPPROPID_LAST 14
namespace cs {
class UsbCameraImpl;
}
@interface UvcControlImpl : NSObject
@property(nonatomic) IOUSBInterfaceInterface190** controlInterface;
@property(nonatomic) uint32_t processingUnitID;
@property(nonatomic) std::weak_ptr<cs::UsbCameraImpl> cppImpl;
// Create from AVCaptureDevice
+ (instancetype)createFromAVCaptureDevice:(AVCaptureDevice*)device status:(CS_Status*)status;
// Initialize with USB vendor/product/location
- (instancetype)initWithVendorId:(uint16_t)vid
productId:(uint16_t)pid
location:(uint32_t)location
status:(CS_Status*)status;
- (void)dealloc;
// Basic property control
- (bool)setProperty:(uint32_t)propID
withValue:(int32_t)value
status:(CS_Status*)status;
- (bool)getProperty:(uint32_t)propID
withValue:(int32_t*)value
status:(CS_Status*)status;
// Auto mode control
- (bool)setAutoProperty:(uint32_t)propID
enabled:(bool)enabled
status:(CS_Status*)status;
- (bool)getAutoProperty:(uint32_t)propID
enabled:(bool*)enabled
status:(CS_Status*)status;
// Property range query
- (bool)getPropertyLimits:(uint32_t)propID
min:(int32_t*)min
max:(int32_t*)max
defValue:(int32_t*)defValue
status:(CS_Status*)status;
@end

View File

@@ -0,0 +1,773 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
// Copyright (c) 2017 Jason von Nieda, Niels Moseley
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import <AVFoundation/AVFoundation.h>
#import "UvcControlImpl.h"
#include "Log.h"
#include "UsbCameraImpl.h"
template <typename S, typename... Args>
inline void NamedLog(UvcControlImpl* objc, unsigned int level,
const char* file, unsigned int line, const S& format,
Args&&... args) {
auto sharedThis = objc.cppImpl.lock();
if (!sharedThis) {
return;
}
wpi::Logger& logger = sharedThis->objcGetLogger();
std::string_view name = sharedThis->GetName();
if (logger.HasLogger() && level >= logger.min_level()) {
cs::NamedLogV(logger, level, file, line, name, format,
fmt::make_format_args(args...));
}
}
#define UVCLOG(level, format, ...) \
NamedLog(self, level, __FILE__, __LINE__, \
format __VA_OPT__(, ) __VA_ARGS__)
#define UVCERROR(format, ...) \
UVCLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCWARNING(format, ...) \
UVCLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCINFO(format, ...) \
UVCLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
#ifdef NDEBUG
#define UVCDEBUG(format, ...) \
do { \
} while (0)
#define UVCDEBUG1(format, ...) \
do { \
} while (0)
#define UVCDEBUG2(format, ...) \
do { \
} while (0)
#define UVCDEBUG3(format, ...) \
do { \
} while (0)
#define UVCDEBUG4(format, ...) \
do { \
} while (0)
#else
#define UVCDEBUG(format, ...) \
UVCLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCDEBUG1(format, ...) \
UVCLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCDEBUG2(format, ...) \
UVCLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCDEBUG3(format, ...) \
UVCLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
#define UVCDEBUG4(format, ...) \
UVCLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
#endif
// USB descriptor for UVC processing unit
struct ProcessingUnitDescriptor
{
uint8_t bLength;
uint8_t bDescriptorType; // CS_INTERFACE 0x24
uint8_t bDescriptorSubtype; // VC_PROCESSING_UNIT 0x05
uint8_t bUnitID;
};
struct propertyInfo_t
{
uint32_t selector; // selector ID
uint32_t unit; // unit (==0 for INPUT TERMINA:, ==1 for PROCESSING UNIT)
uint32_t length; // length (bytes)
};
/** The order of the propertyInfo structure must
be the same as the PROPID numbers in the
openpnp-capture.h header */
const propertyInfo_t propertyInfo[] =
{
{0,0,0},
{CT_EXPOSURE_TIME_ABSOLUTE_CONTROL , 0, 4},
{CT_FOCUS_ABSOLUTE_CONTROL , 0, 2},
{CT_ZOOM_ABSOLUTE_CONTROL , 0, 2},
{PU_WHITE_BALANCE_TEMPERATURE_CONTROL, 1, 2},
{PU_GAIN_CONTROL , 1, 2},
{PU_BRIGHTNESS_CONTROL , 1, 2},
{PU_CONTRAST_CONTROL , 1, 2},
{PU_SATURATION_CONTROL , 1, 2},
{PU_GAMMA_CONTROL , 1, 2},
{PU_HUE_CONTROL , 1, 2},
{PU_SHARPNESS_CONTROL , 1, 2},
{PU_BACKLIGHT_COMPENSATION_CONTROL , 1, 2},
{PU_POWER_LINE_FREQUENCY_CONTROL , 1, 1}
};
@implementation UvcControlImpl {
IOUSBDeviceInterface** _deviceInterface;
}
+ (instancetype)createFromAVCaptureDevice:(AVCaptureDevice*)device status:(CS_Status*)status {
if (!device) {
NSLog(@"UVC: device is nil");
*status = CS_UVC_STATUS_ERROR;
return nil;
}
NSError* error = nil;
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"^UVC\\s+Camera\\s+VendorID\\_([0-9]+)\\s+ProductID\\_([0-9]+)$"
options:0
error:&error];
if (error) {
NSLog(@"UVC: failed to create regex: %@", error);
*status = CS_UVC_STATUS_ERROR;
return nil;
}
NSString* modelID = [device valueForKey:@"modelID"];
if (!modelID) {
NSLog(@"UVC: modelID is nil");
*status = CS_UVC_STATUS_ERROR;
return nil;
}
NSTextCheckingResult* match = [regex firstMatchInString:modelID
options:0
range:NSMakeRange(0, modelID.length)];
if (!match || match.numberOfRanges != 3) {
NSLog(@"UVC: modelID regex match failed");
*status = CS_UVC_STATUS_ERROR;
return nil;
}
NSString* vendorIDStr = [modelID substringWithRange:[match rangeAtIndex:1]];
NSString* productIDStr = [modelID substringWithRange:[match rangeAtIndex:2]];
uint16_t vendorID = (uint16_t)strtoul([vendorIDStr UTF8String], NULL, 10);
uint16_t productID = (uint16_t)strtoul([productIDStr UTF8String], NULL, 10);
uint32_t locationID = 0;
CFMutableDictionaryRef dict = IOServiceMatching(kIOUSBDeviceClassName);
CFDictionarySetValue(dict, CFSTR("idVendor"), (__bridge CFNumberRef)@(vendorID));
CFDictionarySetValue(dict, CFSTR("idProduct"), (__bridge CFNumberRef)@(productID));
io_iterator_t iter = 0;
kern_return_t ioResult = IOServiceGetMatchingServices(kIOMainPortDefault, dict, &iter);
if (ioResult == kIOReturnSuccess) {
io_service_t usbDevice = IOIteratorNext(iter);
while (usbDevice != 0) {
CFTypeRef locationIDRef = IORegistryEntryCreateCFProperty(usbDevice,
CFSTR("locationID"),
kCFAllocatorDefault,
0);
if (locationIDRef) {
locationID = [(__bridge NSNumber*)locationIDRef unsignedIntValue];
CFRelease(locationIDRef);
NSString* uniqueID = [device valueForKey:@"uniqueID"];
NSString* locationIDHex = [NSString stringWithFormat:@"0x%x", locationID];
if ([uniqueID hasPrefix:locationIDHex]) {
IOObjectRelease(usbDevice);
break;
}
}
IOObjectRelease(usbDevice);
usbDevice = IOIteratorNext(iter);
}
IOObjectRelease(iter);
}
UvcControlImpl *instance = [[UvcControlImpl alloc] initWithVendorId:vendorID
productId:productID
location:locationID
status:status];
if (!instance) {
NSLog(@"UVC: failed to create UvcControlImpl, status=%d", *status);
}
return instance;
}
- (instancetype)initWithVendorId:(uint16_t)vid
productId:(uint16_t)pid
location:(uint32_t)location
status:(CS_Status*)status {
self = [super init];
if (self) {
// UVCINFO("Initializing with VID: 0x{:04X}, PID: 0x{:04X}, Location: 0x{:08X}", vid, pid, location);
_deviceInterface = [self findDevice:vid productId:pid location:location];
if (_deviceInterface == nullptr) {
// UVCWARNING("Failed to find device");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return nil;
}
_processingUnitID = [self getProcessingUnitID:_deviceInterface];
_controlInterface = [self createControlInterface:_deviceInterface];
if (_controlInterface == nullptr) {
// UVCWARNING("Failed to create control interface");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return nil;
}
}
return self;
}
- (void)dealloc {
if (_controlInterface != nullptr) {
(*_controlInterface)->USBInterfaceClose(_controlInterface);
(*_controlInterface)->Release(_controlInterface);
}
if (_deviceInterface != nullptr) {
(*_deviceInterface)->Release(_deviceInterface);
}
}
- (IOUSBDeviceInterface**)findDevice:(uint16_t)vid
productId:(uint16_t)pid
location:(uint32_t)location {
CFMutableDictionaryRef dict = IOServiceMatching(kIOUSBDeviceClassName);
io_iterator_t serviceIterator;
kern_return_t result = IOServiceGetMatchingServices((mach_port_t)NULL, dict, &serviceIterator);
if (result != kIOReturnSuccess) {
UVCERROR("findDevice: IOServiceGetMatchingServices failed: {}", result);
return nullptr;
}
io_service_t device;
while((device = IOIteratorNext(serviceIterator)) != 0) {
IOUSBDeviceInterface **deviceInterface = nullptr;
IOCFPlugInInterface **plugInInterface = nullptr;
SInt32 score;
kern_return_t result = IOCreatePlugInInterfaceForService(
device, kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID, &plugInInterface, &score);
if ((result != kIOReturnSuccess) || (plugInInterface == nullptr)) {
UVCERROR("findDevice: Camera control error: {}", result);
IOObjectRelease(device);
continue;
}
HRESULT hr = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
(LPVOID*)&deviceInterface);
if (hr || (deviceInterface == nullptr)) {
(*plugInInterface)->Release(plugInInterface);
IOObjectRelease(device);
UVCERROR("findDevice: QueryInterface failed");
continue;
}
uint16_t vendorID, productID;
uint32_t locationID;
result = (*deviceInterface)->GetDeviceVendor(deviceInterface, &vendorID);
result = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productID);
result = (*deviceInterface)->GetLocationID(deviceInterface, &locationID);
// if 'location' is zero, we won't match on location
// to achieve this, we simply set locationID to zero.
if (location == 0) {
locationID = 0;
}
if ((vendorID == vid) && (productID == pid) && (locationID == location)) {
(*plugInInterface)->Release(plugInInterface);
IOObjectRelease(device);
IOObjectRelease(serviceIterator);
return deviceInterface;
}
(*deviceInterface)->Release(deviceInterface);
(*plugInInterface)->Release(plugInInterface);
IOObjectRelease(device);
}
IOObjectRelease(serviceIterator);
return nullptr;
}
- (uint32_t)getProcessingUnitID:(IOUSBDeviceInterface**)dev {
IOReturn kr;
IOUSBConfigurationDescriptorPtr configDesc;
kr = (*dev)->GetConfigurationDescriptorPtr(dev, 0, &configDesc);
if (kr) {
return 0;
}
UVCDEBUG4("USB descriptor:");
UVCDEBUG4(" length = 0x{:08X}", configDesc->bLength);
UVCDEBUG4(" type = 0x{:08X}", configDesc->bDescriptorType);
UVCDEBUG4(" totalLen = 0x{:08X}", configDesc->wTotalLength);
UVCDEBUG4(" interfaces = 0x{:08X}", configDesc->bNumInterfaces);
uint32_t idx = 0;
uint8_t *ptr = (uint8_t*)configDesc;
// Search for VIDEO/CONTROL interface descriptor
// Class=14, Subclass=1, Protocol=0
// and find the processing unit, if available..
// DescriptorType 0x24, DescriptorSubType 0x5
IOUSBInterfaceDescriptor *iface = NULL;
ProcessingUnitDescriptor *pud = NULL;
bool inVideoControlInterfaceDescriptor = false;
while(idx < configDesc->wTotalLength) {
IOUSBDescriptorHeader *hdr = (IOUSBDescriptorHeader *)&ptr[idx];
switch(hdr->bDescriptorType)
{
case 0x05: // Endpoint descriptor ID
break;
case 0x02: // Configuration descriptor ID
break;
case 0x04: // Interface descriptor ID
iface = (IOUSBInterfaceDescriptor*)&ptr[idx];
if ((iface->bInterfaceClass == 14) &&
(iface->bInterfaceSubClass == 1) &&
(iface->bInterfaceProtocol == 0))
{
inVideoControlInterfaceDescriptor = true;
}
else
{
inVideoControlInterfaceDescriptor = false;
}
break;
case 0x24: // class-specific ID
pud = (ProcessingUnitDescriptor*)&ptr[idx];
if (inVideoControlInterfaceDescriptor)
{
if (pud->bDescriptorSubtype == 0x05)
{
return pud->bUnitID;
}
}
break;
default:
break;
}
idx += hdr->bLength;
}
return 0;
}
- (IOUSBInterfaceInterface190**)createControlInterface:(IOUSBDeviceInterface**)deviceInterface {
IOUSBInterfaceInterface190 **controlInterface;
io_iterator_t interfaceIterator;
IOUSBFindInterfaceRequest interfaceRequest;
interfaceRequest.bInterfaceClass = UVC_CONTROL_INTERFACE_CLASS;
interfaceRequest.bInterfaceSubClass = UVC_CONTROL_INTERFACE_SUBCLASS;
interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
IOReturn result = (*deviceInterface)->CreateInterfaceIterator(deviceInterface,
&interfaceRequest, &interfaceIterator);
if (result != kIOReturnSuccess) {
return nullptr;
}
io_service_t usbInterface;
if ((usbInterface = IOIteratorNext(interfaceIterator)) != 0) {
IOCFPlugInInterface **plugInInterface = nullptr;
SInt32 score;
kern_return_t kr = IOCreatePlugInInterfaceForService(usbInterface,
kIOUSBInterfaceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface,
&score);
kr = IOObjectRelease(usbInterface);
if ((kr != kIOReturnSuccess) || !plugInInterface) {
UVCERROR("createControlInterface: cannot create plug-in {:08X}",
kr);
return nullptr;
}
HRESULT hr = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
(LPVOID*) &controlInterface);
(*plugInInterface)->Release(plugInInterface);
if (hr || !controlInterface) {
UVCERROR("createControlInterface: cannot create device interface {:08X}",
result);
return nullptr;
}
UVCDEBUG3("createControlInterface: created control interface");
return controlInterface;
}
return nullptr;
}
- (bool)sendControlRequest:(IOUSBDevRequest)req {
if (_controlInterface == nullptr) {
UVCERROR("control interface is NULL");
return false;
}
kern_return_t kr;
if (@available(macOS 12.0, *)) {
// macOS 12 doesn't like if we're trying to open USB interface here...
} else {
kr = (*_controlInterface)->USBInterfaceOpen(_controlInterface);
if (kr != kIOReturnSuccess) {
UVCERROR("USBInterfaceOpen failed with error: 0x{:08X}", kr);
return false;
}
}
kr = (*_controlInterface)->ControlRequest(_controlInterface, 0, &req);
if (kr != kIOReturnSuccess) {
// IOKIT error code
#define err_get_system(err) (((err)>>26)&0x3f)
#define err_get_sub(err) (((err)>>14)&0xfff)
#define err_get_code(err) ((err)&0x3fff)
uint32_t code = err_get_code(kr);
uint32_t sys = err_get_system(kr);
uint32_t sub = err_get_sub(kr);
switch(kr)
{
case kIOUSBUnknownPipeErr:
UVCERROR("Pipe ref not recognised");
break;
case kIOUSBTooManyPipesErr:
UVCERROR("Too many pipes");
break;
case kIOUSBEndpointNotFound:
UVCERROR("Endpoint not found");
break;
case kIOUSBConfigNotFound:
UVCERROR("USB configuration not found");
break;
case kIOUSBPipeStalled:
//Note: we don't report this as an error as this happens when
// an unsupported or locked property is set.
UVCDEBUG("Pipe has stalled, error needs to be cleared");
break;
case kIOUSBInterfaceNotFound:
UVCERROR("USB control interface not found");
break;
default:
UVCERROR("ControlRequest failed (KR=sys:sub:code) = {:02Xh}:{:03Xh}:{:04Xh}",
sys, sub, code);
break;
}
if (@available(macOS 12.0, *)) {
// macOS 12 doesn't like if we're trying to close USB interface here...
} else {
kr = (*_controlInterface)->USBInterfaceClose(_controlInterface);
if (kr != kIOReturnSuccess) {
UVCERROR("USBInterfaceClose failed");
}
}
return false;
}
if (@available(macOS 12.0, *)) {
// macOS 12 doesn't like if we're trying to close USB interface here either...
} else {
kr = (*_controlInterface)->USBInterfaceClose(_controlInterface);
if (kr != kIOReturnSuccess) {
UVCERROR("USBInterfaceClose failed");
}
}
return true;
}
- (bool)setData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t)data {
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBOut, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_SET_CUR;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = length;
req.wLenDone = 0;
req.pData = &data;
return [self sendControlRequest:req];
}
- (bool)getData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_GET_CUR;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = length;
req.wLenDone = 0;
req.pData = data;
return [self sendControlRequest:req];
}
- (bool)getMaxData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
IOUSBDevRequest req;
*data = 0;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_GET_MAX;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = length;
req.wLenDone = 0;
req.pData = data;
return [self sendControlRequest:req];
}
- (bool)getMinData:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
IOUSBDevRequest req;
*data = 0;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_GET_MIN;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = length;
req.wLenDone = 0;
req.pData = data;
return [self sendControlRequest:req];
}
- (bool)getDefault:(uint32_t)selector unit:(uint32_t)unit length:(uint32_t)length data:(int32_t*)data {
IOUSBDevRequest req;
*data = 0;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_GET_DEF;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = length;
req.wLenDone = 0;
req.pData = data;
return [self sendControlRequest:req];
}
- (bool)getInfo:(uint32_t)selector unit:(uint32_t)unit data:(uint32_t*)data {
IOUSBDevRequest req;
*data = 0;
req.bmRequestType = USBmakebmRequestType((UInt8)kUSBIn, (UInt8)kUSBClass, (UInt8)kUSBInterface);
req.bRequest = UVC_GET_INFO;
req.wValue = (selector << 8);
req.wIndex = (unit << 8);
req.wLength = 1;
req.wLenDone = 0;
req.pData = data;
return [self sendControlRequest:req];
}
- (bool)setProperty:(uint32_t)propID withValue:(int32_t)value status:(CS_Status*)status {
if (_controlInterface == nullptr) {
UVCERROR("control interface is NULL");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return false;
}
bool ok = false;
if (propID < CAPPROPID_LAST) {
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
ok = [self setData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:value];
if (!ok) {
UVCWARNING("Failed to set property {}", propID);
}
} else {
UVCWARNING("Invalid property ID: {}", propID);
}
return ok;
}
- (bool)getProperty:(uint32_t)propID withValue:(int32_t*)value status:(CS_Status*)status {
if (_controlInterface == nullptr) {
UVCERROR("control interface is NULL");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return false;
}
bool ok = false;
if (propID < CAPPROPID_LAST) {
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
ok = [self getData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:value];
switch(propertyInfo[propID].length) {
case 2:
*value = static_cast<int16_t>(*value);
break;
case 1:
*value = static_cast<int8_t>(*value);
break;
default:
break;
}
if (!ok) {
UVCWARNING("Failed to get property {}", propID);
}
} else {
UVCWARNING("Invalid property ID: {}", propID);
}
return ok;
}
- (bool)setAutoProperty:(uint32_t)propID enabled:(bool)enabled status:(CS_Status*)status {
if (_controlInterface == nullptr) {
UVCERROR("control interface is NULL");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return false;
}
int32_t value = enabled ? 1 : 0;
switch(propID) {
case CAPPROPID_EXPOSURE:
return [self setData:CT_AE_MODE_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:enabled ? 0x8 : 0x1];
case CAPPROPID_WHITEBALANCE:
return [self setData:PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL unit:_processingUnitID length:1 data:value];
case CAPPROPID_FOCUS:
return [self setData:CT_FOCUS_AUTO_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:value];
default:
return false;
}
}
- (bool)getAutoProperty:(uint32_t)propID enabled:(bool*)enabled status:(CS_Status*)status {
if (_controlInterface == nullptr) {
UVCERROR("control interface is NULL");
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return false;
}
int32_t value;
switch(propID) {
case CAPPROPID_EXPOSURE:
if ([self getData:CT_AE_MODE_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:&value]) {
// value = 1 -> manual mode
// 2 -> auto mode (I haven't seen this in the wild)
// 4 -> shutter priority mode (haven't seen this)
// 8 -> aperature prioritry mode (seen this used)
value &= 0xFF;
*enabled = (value==1) ? false : true;
return true;
}
return false;
case CAPPROPID_WHITEBALANCE:
if ([self getData:PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL unit:_processingUnitID length:1 data:&value]) {
value &= 0xFF;
*enabled = (value==1) ? true : false;
UVCDEBUG3("White balance auto mode: {}", *enabled ? "enabled" : "disabled");
return true;
}
return false;
case CAPPROPID_FOCUS:
if ([self getData:CT_FOCUS_AUTO_CONTROL unit:UVC_INPUT_TERMINAL_ID length:1 data:&value]) {
value &= 0xFF;
*enabled = (value==1) ? true : false;
UVCDEBUG3("Focus auto mode: {}", *enabled ? "enabled" : "disabled");
return true;
}
return false;
default:
UVCWARNING("Unsupported auto property ID: {}", propID);
return false;
}
}
- (bool)getPropertyLimits:(uint32_t)propID min:(int32_t*)min max:(int32_t*)max defValue:(int32_t*)defValue status:(CS_Status*)status {
if (_controlInterface == nullptr) {
*status = CS_UVC_STATUS_DEVICE_DISCONNECTED;
return false;
}
bool ok = true;
if (propID < CAPPROPID_LAST) {
uint32_t unit = (propertyInfo[propID].unit == 0) ? UVC_INPUT_TERMINAL_ID : _processingUnitID;
if (![self getMinData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:min]) {
ok = false;
}
if (![self getMaxData:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:max]) {
ok = false;
}
if (![self getDefault:propertyInfo[propID].selector unit:unit length:propertyInfo[propID].length data:defValue]) {
ok = false;
}
switch(propertyInfo[propID].length) {
case 2:
*min = static_cast<int16_t>(*min);
*max = static_cast<int16_t>(*max);
*defValue = static_cast<int16_t>(*defValue);
break;
case 1:
*min = static_cast<int8_t>(*min);
*max = static_cast<int8_t>(*max);
*defValue = static_cast<int8_t>(*defValue);
break;
default:
break;
}
} else {
UVCWARNING("getPropertyLimits: property ID out of bounds");
ok = false;
}
return ok;
}
- (void)reportCapabilities:(uint32_t)selector unit:(uint32_t)unit {
uint32_t info;
[self getInfo:selector unit:unit data:&info];
if (info & 0x01) {
UVCDEBUG4("GET ");
}
if (info & 0x02) {
UVCDEBUG4("SET ");
}
if (info & 0x04) {
UVCDEBUG4("DISABLED ");
}
if (info & 0x08) {
UVCDEBUG4("AUTO-UPD ");
}
if (info & 0x10) {
UVCDEBUG4("ASYNC ");
}
if (info & 0x20) {
UVCDEBUG4("DISCOMMIT");
}
UVCDEBUG4("");
}
@end

View File

@@ -32,6 +32,13 @@ nativeUtils.enableSourceLink()
nativeUtils.wpi.addMacMinimumVersionArg()
nativeUtils.platformConfigs.each {
if (it.name.contains('osx')) {
it.linker.getArgs().add('-framework')
it.linker.getArgs().add('IOKit')
}
}
// Compress debug info on Linux
nativeUtils.platformConfigs.each {
if (it.name.contains('linux')) {