mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[cscore] Add UVC Protocol Support for USB Camera Controls on macOS (#7926)
This commit is contained in:
@@ -46,6 +46,7 @@ objc_library(
|
||||
"Foundation",
|
||||
"CoreMedia",
|
||||
"CoreVideo",
|
||||
"IOKit",
|
||||
],
|
||||
tags = ["manual"],
|
||||
deps = [
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
129
cscore/src/main/native/objcpp/UvcControlImpl.h
Normal file
129
cscore/src/main/native/objcpp/UvcControlImpl.h
Normal 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
|
||||
773
cscore/src/main/native/objcpp/UvcControlImpl.mm
Normal file
773
cscore/src/main/native/objcpp/UvcControlImpl.mm
Normal 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
|
||||
@@ -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')) {
|
||||
|
||||
Reference in New Issue
Block a user