Skip to content

Commit

Permalink
Disambiguate cameras with same product/vendor ids (issue Itaybre#59)
Browse files Browse the repository at this point in the history
Fix issue where `AVCaptureDevice.usbDevice()` would return a USBDevice
pointing to the wrong camera if multiple cameras with the same product
id and vendor id are attached to the computer.

The new approach is to use an iterator to lookup the cameras with the
given vendor and product ids, and then loop through the results and
match against their property dictionaries against the
avDevice.localizedName since that seems to be the only reliable way to
disambiguate between different cameras with otherwise similar
properties.
  • Loading branch information
jazzychad committed May 4, 2022
1 parent edb2d4f commit 55729c6
Showing 1 changed file with 46 additions and 2 deletions.
48 changes: 46 additions & 2 deletions CameraController/UVC/Extensions/AVCaptureDevice+USB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,61 @@ typealias DeviceInterfacePointer = UnsafeMutablePointer<UnsafeMutablePointer<IOU

extension AVCaptureDevice {
func usbDevice() throws -> USBDevice {
var camera: io_service_t = 0
let cameraInformation = try self.modelID.extractCameraInformation()
let dictionary: NSMutableDictionary = IOServiceMatching("IOUSBDevice") as NSMutableDictionary
dictionary["idVendor"] = cameraInformation.vendorId
dictionary["idProduct"] = cameraInformation.productId

var interfaceRef: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBInterfaceInterface190>>?
let camera: io_service_t = IOServiceGetMatchingService(kIOMasterPortDefault, dictionary)
// adding other keys to this dictionary like kUSBProductString, kUSBVendorString, etc don't
// seem to have any affect on using IOServiceGetMatchingService to get the correct camera,
// so we instead get an iterator for the matching services based on idVendor and idProduct
// and fetch their property dicts and then match against the more specific values

var iter: io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &iter) == kIOReturnSuccess {
var cameraCandidate: io_service_t
cameraCandidate = IOIteratorNext(iter)
while (cameraCandidate != 0) {
var propsRef: Unmanaged<CFMutableDictionary>? = nil

if IORegistryEntryCreateCFProperties(cameraCandidate, &propsRef, kCFAllocatorDefault, 0) == kIOReturnSuccess {
if let properties = propsRef?.takeRetainedValue() {

// these are common keys that might have the device name stored in the propery dictionary
let keysToTry: [String] = ["kUSBProductString", "kUSBVendorString", "USB Product Name", "USB Vendor Name"]

var found: Bool = false
for key in keysToTry {
if let cameraName = (properties as NSDictionary)[key] as? String {
if cameraName == self.localizedName {
// we have a match, use this as the camera
camera = cameraCandidate
found = true
// break out of `for key in keysToTry`
break
}
}
}
if found {
// break out of `while (cameraCandidate != 0)`
break
}
}
}
cameraCandidate = IOIteratorNext(iter)
}
}

// if we haven't found a camera after looping through the iterator, fallback on GetMatchingService method
if camera == 0 {
camera = IOServiceGetMatchingService(kIOMasterPortDefault, dictionary)
}
defer {
let code: kern_return_t = IOObjectRelease(camera)
assert( code == kIOReturnSuccess )
}
var interfaceRef: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBInterfaceInterface190>>?
var configDesc: IOUSBConfigurationDescriptorPtr?
try camera.ioCreatePluginInterfaceFor(service: kIOUSBDeviceUserClientTypeID) {
let deviceInterface: DeviceInterfacePointer = try $0.getInterface(uuid: kIOUSBDeviceInterfaceID)
Expand Down

0 comments on commit 55729c6

Please sign in to comment.