Wednesday, December 24, 2025

usb – How to locate a UVC device with unique_id from AVCaptureDevice?

I’m writing a program to control a PTZ camera connected via USB.

I can get access to target camera’s unique_id, and also other infos provided by AVFoundation. But I don’t know how to locate my target USB device to send a UVC ControlRequest.

There’s many Cameras with same VendorID and ProductID connected at a time, so I need a more exact way to find out which device is my target.

It looks that the unique_id provided is (locationID<<32|VendorID<<16|ProductID) as hex string, but I’m not sure if I can always assume this behavior won’t change.

Is there’s a document declares how AVFoundation generate the unique_id for USB camera, so I can assume this convert will always work? Or is there’s a way to send a PTZ control request to AVCaptureDevice?

https://stackoverflow.com/questions/40006908/usb-interface-of-an-avcapturedevice

I have seen this similar question. But I’m worrying that Exacting LocationID+VendorID+ProductID from unique_id seems like programming to implementation instead of interface. So, if there’s any other better way to control my camera?

here’s my example code for getting unique_id:

//
// camera_unique_id_test.mm
// clang++ -framework AVFoundation -framework CoreMedia -framework Foundation
// camera_unique_id_test.mm -o camera_unique_id_test
//

#include <iostream>
#include <string>
#include <vector>

#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>

struct CameraInfo {
  std::string uniqueId;
  std::string localizedName;
  std::string modelId;
  bool isConnected;
  bool isDefault;
};

std::vector<CameraInfo> getAllCameraDevices() {
  std::vector<CameraInfo> cameras;

  @autoreleasepool {
    // 获取所有视频输入设备
    NSArray<AVCaptureDevice*>* devices =
        [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

    // 获取默认摄像头设备
    AVCaptureDevice* defaultDevice =
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    // 遍历所有设备
    for (AVCaptureDevice* device in devices) {
      CameraInfo info;

      // 获取unique_id
      info.uniqueId = std::string([device.uniqueID UTF8String]);

      // 获取设备名称
      info.localizedName = std::string([device.localizedName UTF8String]);

      // 获取模型ID
      info.modelId = std::string([device.modelID UTF8String]);

      // 检查是否已连接
      info.isConnected = device.connected;

      // 检查是否为默认设备
      info.isDefault = [device.uniqueID isEqualToString:defaultDevice.uniqueID];

      cameras.push_back(info);
    }
  }

  return cameras;
}

int main(int argc, char* argv[]) {
  std::vector<CameraInfo> cameras = getAllCameraDevices();

  for (size_t i = 0; i < cameras.size(); i++) {
    const CameraInfo& camera = cameras[i];
    std::cout << "   设备 " << (i + 1) << ":" << std::endl;
    std::cout << "     unique_id:     " << camera.uniqueId << std::endl;
    std::cout << "     设备名称:      " << camera.localizedName << std::endl;
    std::cout << "     模型ID:        " << camera.modelId << std::endl;
    std::cout << "     连接状态:      "
              << (camera.isConnected ? "已连接" : "未连接") << std::endl;
    std::cout << "     是否默认:      " << (camera.isDefault ? "是" : "否")
              << std::endl;
    std::cout << std::endl;
  }

  return 0;
}

and here’s my code for UVC control:

// clang++ -framework Foundation -framework IOKit uvc_test.cpp -o uvc_test

#include <iostream>

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USB.h>

CFStringRef CreateCFStringFromIORegistryKey(io_service_t ioService,
                                            const char* key) {
  CFStringRef keyString = CFStringCreateWithCString(kCFAllocatorDefault, key,
                                                    kCFStringEncodingUTF8);
  if (!keyString)
    return nullptr;

  CFStringRef result = static_cast<CFStringRef>(
      IORegistryEntryCreateCFProperty(ioService, keyString, kCFAllocatorDefault,
                                      kIORegistryIterateRecursively));
  CFRelease(keyString);
  return result;
}

std::string GetStringFromIORegistry(io_service_t ioService, const char* key) {
  CFStringRef cfString = CreateCFStringFromIORegistryKey(ioService, key);
  if (!cfString)
    return "";

  char buffer[256];
  Boolean success = CFStringGetCString(cfString, buffer, sizeof(buffer),
                                       kCFStringEncodingUTF8);
  CFRelease(cfString);

  return success ? std::string(buffer) : std::string("");
}

uint32_t GetUInt32FromIORegistry(io_service_t ioService, const char* key) {
  CFStringRef keyString = CFStringCreateWithCString(kCFAllocatorDefault, key,
                                                    kCFStringEncodingUTF8);
  if (!keyString)
    return 0;

  CFNumberRef number = static_cast<CFNumberRef>(
      IORegistryEntryCreateCFProperty(ioService, keyString, kCFAllocatorDefault,
                                      kIORegistryIterateRecursively));
  CFRelease(keyString);

  if (!number)
    return 0;

  uint32_t value = 0;
  CFNumberGetValue(number, kCFNumberSInt32Type, &value);
  CFRelease(number);
  return value;
}

int main() {
  // Get matching dictionary for USB devices
  CFMutableDictionaryRef matchingDict =
      IOServiceMatching(kIOUSBDeviceClassName);

  // Get iterator for matching services
  io_iterator_t serviceIterator;
  IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict,
                               &serviceIterator);

  // Iterate through matching devices
  io_service_t usbService;
  while ((usbService = IOIteratorNext(serviceIterator))) {
    uint32_t locationId = GetUInt32FromIORegistry(usbService, "locationID");
    uint32_t vendorId = GetUInt32FromIORegistry(usbService, "idVendor");
    uint32_t productId = GetUInt32FromIORegistry(usbService, "idProduct");

    IOCFPlugInInterface** plugInInterface = nullptr;
    IOUSBDeviceInterface** deviceInterface = nullptr;
    SInt32 score;

    // Get device plugin interface
    IOCreatePlugInInterfaceForService(usbService, kIOUSBDeviceUserClientTypeID,
                                      kIOCFPlugInInterfaceID, &plugInInterface,
                                      &score);
    // Get device interface

    (*plugInInterface)
        ->QueryInterface(plugInInterface,
                         CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
                         (LPVOID*)&deviceInterface);

    (*plugInInterface)->Release(plugInInterface);

    // Try to find UVC control interface using CreateInterfaceIterator
    io_iterator_t interfaceIterator;
    IOUSBFindInterfaceRequest interfaceRequest;
    interfaceRequest.bInterfaceClass = kUSBVideoInterfaceClass;      // 14
    interfaceRequest.bInterfaceSubClass = kUSBVideoControlSubClass;  // 1
    interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
    interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;

    (*deviceInterface)
        ->CreateInterfaceIterator(deviceInterface, &interfaceRequest,
                                  &interfaceIterator);
    (*deviceInterface)->Release(deviceInterface);

    io_service_t usbInterface = IOIteratorNext(interfaceIterator);
    IOObjectRelease(interfaceIterator);

    if (usbInterface) {
      std::cout << "Get UVC device with:" << std::endl;
      std::cout << "locationId: " << std::hex << locationId << std::endl;
      std::cout << "vendorId: " << std::hex << vendorId << std::endl;
      std::cout << "productId: " << std::hex << productId << std::endl
                << std::endl;
      IOObjectRelease(usbInterface);
    }

    IOObjectRelease(usbService);
  }

  IOObjectRelease(serviceIterator);
}

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles