// // This code was written by Dominic Szablewski: http://phoboslab.org/log/2009/07/uvc-camera-control-for-mac-os-x // // It is in the public domain: // // "Consider this as public domain. Do whatever you want with it. I'm not responsible if your camera explodes or // for any other damages this software might cause!" // #import "UVCCameraControl.h" #import "UVCCameraControl.hpp" void configureCamera(int vendorID, int productID, int autoExposure, float exposure, float contrast, float saturation, float sharpness, int autoWhiteBalance, float whiteBalance) { UVCCameraControl* cameraControl = [[UVCCameraControl alloc] initWithVendorID:0x05ac productID:0x8510]; [cameraControl setAutoExposure:autoExposure]; [cameraControl setExposure:exposure]; [cameraControl setContrast:contrast]; [cameraControl setSaturation:saturation]; [cameraControl setSharpness:sharpness]; [cameraControl setAutoWhiteBalance:autoWhiteBalance]; [cameraControl setWhiteBalance:whiteBalance]; } const uvc_controls_t uvc_controls = { .autoExposure = { .unit = UVC_INPUT_TERMINAL_ID, .selector = 0x02, .size = 1, }, .exposure = { .unit = UVC_INPUT_TERMINAL_ID, .selector = 0x04, .size = 4, }, .brightness = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x02, .size = 2, }, .contrast = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x03, .size = 2, }, .gain = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x04, .size = 2, }, .saturation = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x07, .size = 2, }, .sharpness = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x08, .size = 2, }, .whiteBalance = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x0A, .size = 2, }, .autoWhiteBalance = { .unit = UVC_PROCESSING_UNIT_ID, .selector = 0x0B, .size = 1, }, }; @implementation UVCCameraControl - (id)initWithLocationID:(UInt32)locationID { if( self = [super init] ) { interface = NULL; // Find All USB Devices, get their locationId and check if it matches the requested one CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); io_iterator_t serviceIterator; IOServiceGetMatchingServices( kIOMasterPortDefault, matchingDict, &serviceIterator ); io_service_t camera; while( camera = IOIteratorNext(serviceIterator) ) { // Get DeviceInterface IOUSBDeviceInterface **deviceInterface = NULL; IOCFPlugInInterface **plugInInterface = NULL; SInt32 score; kern_return_t kr = IOCreatePlugInInterfaceForService( camera, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score ); if( (kIOReturnSuccess != kr) || !plugInInterface ) { NSLog( @"CameraControl Error: IOCreatePlugInInterfaceForService returned 0x%08x.", kr ); continue; } HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceInterface ); (*plugInInterface)->Release(plugInInterface); if( res || deviceInterface == NULL ) { NSLog( @"CameraControl Error: QueryInterface returned %d.\n", (int)res ); continue; } UInt32 currentLocationID = 0; (*deviceInterface)->GetLocationID(deviceInterface, ¤tLocationID); if( currentLocationID == locationID ) { // Yep, this is the USB Device that was requested! interface = [self getControlInferaceWithDeviceInterface:deviceInterface]; return self; } } // end while } return self; } - (id)initWithVendorID:(long)vendorID productID:(long)productID { if( self = [super init] ) { interface = NULL; // Find USB Device CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName); CFNumberRef numberRef; numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorID); CFDictionarySetValue( matchingDict, CFSTR(kUSBVendorID), numberRef ); CFRelease(numberRef); numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productID); CFDictionarySetValue( matchingDict, CFSTR(kUSBProductID), numberRef ); CFRelease(numberRef); io_service_t camera = IOServiceGetMatchingService( kIOMasterPortDefault, matchingDict ); // Get DeviceInterface IOUSBDeviceInterface **deviceInterface = NULL; IOCFPlugInInterface **plugInInterface = NULL; SInt32 score; kern_return_t kr = IOCreatePlugInInterfaceForService( camera, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score ); if( (kIOReturnSuccess != kr) || !plugInInterface ) { NSLog( @"CameraControl Error: IOCreatePlugInInterfaceForService returned 0x%08x.", kr ); return self; } HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &deviceInterface ); (*plugInInterface)->Release(plugInInterface); if( res || deviceInterface == NULL ) { NSLog( @"CameraControl Error: QueryInterface returned %d.\n", (int)res ); return self; } interface = [self getControlInferaceWithDeviceInterface:deviceInterface]; } return self; } - (IOUSBInterfaceInterface190 **)getControlInferaceWithDeviceInterface:(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 success = (*deviceInterface)->CreateInterfaceIterator( deviceInterface, &interfaceRequest, &interfaceIterator ); if( success != kIOReturnSuccess ) { return NULL; } io_service_t usbInterface; HRESULT result; if( usbInterface = IOIteratorNext(interfaceIterator) ) { IOCFPlugInInterface **plugInInterface = NULL; //Create an intermediate plug-in SInt32 score; kern_return_t kr = IOCreatePlugInInterfaceForService( usbInterface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score ); //Release the usbInterface object after getting the plug-in kr = IOObjectRelease(usbInterface); if( (kr != kIOReturnSuccess) || !plugInInterface ) { NSLog( @"CameraControl Error: Unable to create a plug-in (%08x)\n", kr ); return NULL; } //Now create the device interface for the interface result = (*plugInInterface)->QueryInterface( plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID *) &controlInterface ); //No longer need the intermediate plug-in (*plugInInterface)->Release(plugInInterface); if( result || !controlInterface ) { NSLog( @"CameraControl Error: Couldn’t create a device interface for the interface (%08x)", (int) result ); return NULL; } return controlInterface; } return NULL; } - (BOOL)sendControlRequest:(IOUSBDevRequest)controlRequest { if( !interface ){ NSLog( @"CameraControl Error: No interface to send request" ); return NO; } //Now open the interface. This will cause the pipes associated with //the endpoints in the interface descriptor to be instantiated kern_return_t kr = (*interface)->ControlRequest( interface, 0, &controlRequest ); if( kr != kIOReturnSuccess ) { NSLog( @"CameraControl Error: Control request failed: %08x", kr ); return NO; } return YES; } - (BOOL)setData:(long)value withLength:(int)length forSelector:(int)selector at:(int)unitId { IOUSBDevRequest controlRequest; controlRequest.bmRequestType = USBmakebmRequestType( kUSBOut, kUSBClass, kUSBInterface ); controlRequest.bRequest = UVC_SET_CUR; controlRequest.wValue = (selector << 8) | 0x00; controlRequest.wIndex = (unitId << 8) | 0x00; controlRequest.wLength = length; controlRequest.wLenDone = 0; controlRequest.pData = &value; return [self sendControlRequest:controlRequest]; } - (long)getDataFor:(int)type withLength:(int)length fromSelector:(int)selector at:(int)unitId { long value = 0; IOUSBDevRequest controlRequest; controlRequest.bmRequestType = USBmakebmRequestType( kUSBIn, kUSBClass, kUSBInterface ); controlRequest.bRequest = type; controlRequest.wValue = (selector << 8) | 0x00; controlRequest.wIndex = (unitId << 8) | 0x00; controlRequest.wLength = length; controlRequest.wLenDone = 0; controlRequest.pData = &value; BOOL success = [self sendControlRequest:controlRequest]; return ( success ? value : 0 ); } // Get Range (min, max) - (uvc_range_t)getRangeForControl:(uvc_control_info_t *)control { uvc_range_t range = { 0, 0 }; range.min = [self getDataFor:UVC_GET_MIN withLength:control->size fromSelector:control->selector at:control->unit]; range.max = [self getDataFor:UVC_GET_MAX withLength:control->size fromSelector:control->selector at:control->unit]; return range; } // Used to de-/normalize values - (float)mapValue:(float)value fromMin:(float)fromMin max:(float)fromMax toMin:(float)toMin max:(float)toMax { return toMin + (toMax - toMin) * ((value - fromMin) / (fromMax - fromMin)); } // Get a normalized value - (float)getValueForControl:(uvc_control_info_t *)control { // TODO: Cache the range somewhere? uvc_range_t range = [self getRangeForControl:control]; int intval = [self getDataFor:UVC_GET_CUR withLength:control->size fromSelector:control->selector at:control->unit]; return [self mapValue:intval fromMin:range.min max:range.max toMin:0 max:1]; } // Set a normalized value - (BOOL)setValue:(float)value forControl:(uvc_control_info_t *)control { // TODO: Cache the range somewhere? uvc_range_t range = [self getRangeForControl:control]; int intval = [self mapValue:value fromMin:0 max:1 toMin:range.min max:range.max]; return [self setData:intval withLength:control->size forSelector:control->selector at:control->unit]; } // ================================================================ // Set/Get the actual values for the camera // - (BOOL)setAutoExposure:(BOOL)enabled { int intval = (enabled ? 0x08 : 0x01); // "auto exposure modes" ar NOT boolean (on|off) as it seems return [self setData:intval withLength:uvc_controls.autoExposure.size forSelector:uvc_controls.autoExposure.selector at:uvc_controls.autoExposure.unit]; } - (BOOL)getAutoExposure { int intval = [self getDataFor:UVC_GET_CUR withLength:uvc_controls.autoExposure.size fromSelector:uvc_controls.autoExposure.selector at:uvc_controls.autoExposure.unit]; return ( intval == 0x08 ? YES : NO ); } - (BOOL)setExposure:(float)value { value = 1 - value; return [self setValue:value forControl:&uvc_controls.exposure]; } - (float)getExposure { float value = [self getValueForControl:&uvc_controls.exposure]; return 1 - value; } - (BOOL)setGain:(float)value { return [self setValue:value forControl:&uvc_controls.gain]; } - (float)getGain { return [self getValueForControl:&uvc_controls.gain]; } - (BOOL)setBrightness:(float)value { return [self setValue:value forControl:&uvc_controls.brightness]; } - (float)getBrightness { return [self getValueForControl:&uvc_controls.brightness]; } - (BOOL)setContrast:(float)value { return [self setValue:value forControl:&uvc_controls.contrast]; } - (float)getContrast { return [self getValueForControl:&uvc_controls.contrast]; } - (BOOL)setSaturation:(float)value { return [self setValue:value forControl:&uvc_controls.saturation]; } - (float)getSaturation { return [self getValueForControl:&uvc_controls.saturation]; } - (BOOL)setSharpness:(float)value { return [self setValue:value forControl:&uvc_controls.sharpness]; } - (float)getSharpness { return [self getValueForControl:&uvc_controls.sharpness]; } - (BOOL)setAutoWhiteBalance:(BOOL)enabled { int intval = (enabled ? 0x01 : 0x00); return [self setData:intval withLength:uvc_controls.autoWhiteBalance.size forSelector:uvc_controls.autoWhiteBalance.selector at:uvc_controls.autoWhiteBalance.unit]; } - (BOOL)getAutoWhiteBalance { int intval = [self getDataFor:UVC_GET_CUR withLength:uvc_controls.autoWhiteBalance.size fromSelector:uvc_controls.autoWhiteBalance.selector at:uvc_controls.autoWhiteBalance.unit]; return ( intval ? YES : NO ); } - (BOOL)setWhiteBalance:(float)value { return [self setValue:value forControl:&uvc_controls.whiteBalance]; } - (float)getWhiteBalance { return [self getValueForControl:&uvc_controls.whiteBalance]; } @end