Skip to content

Commit 55729c6

Browse files
committed
Disambiguate cameras with same product/vendor ids (issue #59)
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.
1 parent edb2d4f commit 55729c6

1 file changed

Lines changed: 46 additions & 2 deletions

File tree

CameraController/UVC/Extensions/AVCaptureDevice+USB.swift

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,61 @@ typealias DeviceInterfacePointer = UnsafeMutablePointer<UnsafeMutablePointer<IOU
3030

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

38-
var interfaceRef: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBInterfaceInterface190>>?
39-
let camera: io_service_t = IOServiceGetMatchingService(kIOMasterPortDefault, dictionary)
39+
// adding other keys to this dictionary like kUSBProductString, kUSBVendorString, etc don't
40+
// seem to have any affect on using IOServiceGetMatchingService to get the correct camera,
41+
// so we instead get an iterator for the matching services based on idVendor and idProduct
42+
// and fetch their property dicts and then match against the more specific values
43+
44+
var iter: io_iterator_t = 0
45+
if IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &iter) == kIOReturnSuccess {
46+
var cameraCandidate: io_service_t
47+
cameraCandidate = IOIteratorNext(iter)
48+
while (cameraCandidate != 0) {
49+
var propsRef: Unmanaged<CFMutableDictionary>? = nil
50+
51+
if IORegistryEntryCreateCFProperties(cameraCandidate, &propsRef, kCFAllocatorDefault, 0) == kIOReturnSuccess {
52+
if let properties = propsRef?.takeRetainedValue() {
53+
54+
// these are common keys that might have the device name stored in the propery dictionary
55+
let keysToTry: [String] = ["kUSBProductString", "kUSBVendorString", "USB Product Name", "USB Vendor Name"]
56+
57+
var found: Bool = false
58+
for key in keysToTry {
59+
if let cameraName = (properties as NSDictionary)[key] as? String {
60+
if cameraName == self.localizedName {
61+
// we have a match, use this as the camera
62+
camera = cameraCandidate
63+
found = true
64+
// break out of `for key in keysToTry`
65+
break
66+
}
67+
}
68+
}
69+
if found {
70+
// break out of `while (cameraCandidate != 0)`
71+
break
72+
}
73+
}
74+
}
75+
cameraCandidate = IOIteratorNext(iter)
76+
}
77+
}
78+
79+
// if we haven't found a camera after looping through the iterator, fallback on GetMatchingService method
80+
if camera == 0 {
81+
camera = IOServiceGetMatchingService(kIOMasterPortDefault, dictionary)
82+
}
4083
defer {
4184
let code: kern_return_t = IOObjectRelease(camera)
4285
assert( code == kIOReturnSuccess )
4386
}
87+
var interfaceRef: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBInterfaceInterface190>>?
4488
var configDesc: IOUSBConfigurationDescriptorPtr?
4589
try camera.ioCreatePluginInterfaceFor(service: kIOUSBDeviceUserClientTypeID) {
4690
let deviceInterface: DeviceInterfacePointer = try $0.getInterface(uuid: kIOUSBDeviceInterfaceID)

0 commit comments

Comments
 (0)