Qt在Mac上实现USB热插拔检测

Mac 上的 USB 热插拔检测,可以通过 IOKit framework 来实现,网上也有对应的代码,下面是我的一个测试 Demo:

#include <stdlib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/usb/IOUSBLib.h>

// IOServiceAddMatchingNotification 回调函数
// refCon 是注册时传递的参数,比如对象指针等
void deviceAdded(void *refCon, io_iterator_t iterator) {
    io_service_t usb_device;
    while (usb_device = IOIteratorNext(iterator)) {
        // 设备名称
        CFStringRef device_name = static_cast<CFStringRef>(IORegistryEntryCreateCFProperty(usb_device, CFSTR(kUSBProductString), kCFAllocatorDefault, 0));
        if (!device_name) {
            device_name = static_cast<CFStringRef>(IORegistryEntryCreateCFProperty(usb_device, CFSTR(kUSBVendorString), kCFAllocatorDefault, 0));
        }
        if (!device_name) {
            device_name = CFStringCreateWithCString(kCFAllocatorDefault, "<Unknown>", kCFStringEncodingUTF8);
        }
        ssize_t len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(device_name), kCFStringEncodingUTF8);
        char *buf = new char[len];
        CFStringGetCString(device_name, buf, len, kCFStringEncodingUTF8);
        fprintf(stderr, "Device added: %s.\n", buf);
        delete buf;
        CFRelease(device_name);

        // 获取 vip 和 pid
        CFTypeRef cf_vendor, cf_product;
        cf_vendor = (CFTypeRef)IORegistryEntrySearchCFProperty(usb_device, kIOServicePlane, CFSTR("idVendor"), kCFAllocatorDefault,
                                                               kIORegistryIterateRecursively | kIORegistryIterateParents);

        cf_product = (CFTypeRef)IORegistryEntrySearchCFProperty(usb_device, kIOServicePlane, CFSTR("idProduct"), kCFAllocatorDefault,
                                                                kIORegistryIterateRecursively | kIORegistryIterateParents);
        SInt32 vendor_id = 0, product_id = 0;
        if (cf_vendor && cf_product && CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberSInt32Type, &vendor_id) &&
                CFNumberGetValue((CFNumberRef)cf_product, kCFNumberSInt32Type, &product_id)) {
            fprintf(stderr, "Device vid 0x%04x, pid 0x%04x.\n", vendor_id, product_id);
        }
        if (cf_vendor)
            CFRelease(cf_vendor);
        if (cf_product)
            CFRelease(cf_product);

        IOObjectRelease(usb_device);
    }
}

int main() {
    // 创建 I/O Kit 通知端口
    IONotificationPortRef notify_prot = IONotificationPortCreate(kIOMasterPortDefault);

    // 创建匹配字典,可以指定要监听的设备的厂商 ID 和产品 ID
    CFMutableDictionaryRef match_dict = IOServiceMatching(kIOUSBDeviceClassName);
    // SInt32 vendor_id = 0x2717;
    // SInt32 product_id = 0xFF08;
    // CFDictionarySetValue(match_dict, CFSTR(kUSBVendorID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor_id));
    // CFDictionarySetValue(match_dict, CFSTR(kUSBProductID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product_id));

    // 创建异步通知端口
    io_iterator_t add_iterator;
    kern_return_t result;
    // kIOMatchedNotification 添加,kIOTerminatedNotification 移除
    result = IOServiceAddMatchingNotification(notify_prot, kIOMatchedNotification, match_dict, deviceAdded, nullptr, &add_iterator);
    if (result != KERN_SUCCESS) {
        fprintf(stderr, "IOServiceAddMatchingNotification error: %d. \n", result);
        return -1;
    }
    // 初始化的时候要调用一次,不然没回调,也可以在这时枚举当前已有设备
    // 可以使用空的处理来跳过
    deviceAdded(nullptr, add_iterator);

    // 将通知端口连接到一个运行循环结构中
    // 运行循环属于 Core Foundation 变成模型,实现了消息循环,当消息到达通知端口时,用户提供的回调函数会被调用
    CFRunLoopSourceRef run_loop = IONotificationPortGetRunLoopSource(notify_prot);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop, kCFRunLoopDefaultMode);

    // 进入主运行循环,等待设备插入或拔出事件
    // 如果是放到 Qt 事件循环里就不需要这个
    CFRunLoopRun();

    // 释放
    IONotificationPortDestroy(notify_prot);

    return 0;
}

Qt qmake 在使用对应的 framework 时 pro 需要添加 LIBS:

macos{
    LIBS += -framework IOKit -framework CoreFoundation
}

但是,USB 插拔消息来的时候,QCamera 还没法枚举到对应的摄像头,这个和 Windows 是一样的,需要注册相机的插拔回调才行。

Qt5 的 QCamera 后端实现在 Mac 上使用的 AVFoundation framework,我又找了一个相机插拔的代码(pro 加上 -framework AVFoundation):

#include <AVFoundation/AVFoundation.h>
#include <objc/objc.h>
#include <QCameraInfo>
#include <QDebug>

id attachHandle = nullptr;
id detachHandle = nullptr;

void init()
{
    // 注册之前必须先调用AVCaptureDevice
    [AVCaptureDevice devices];
    // 摄像头可用事件
    attachHandle = [[NSNotificationCenter defaultCenter]
            addObserverForName:AVCaptureDeviceWasConnectedNotification
                               object:nil
                               queue:nil
                               usingBlock:^(NSNotification *){
        qDebug()<<"add"<<QCameraInfo::availableCameras().size();
    }];
    // 摄像头不可用
    detachHandle = [[NSNotificationCenter defaultCenter]
            addObserverForName:AVCaptureDeviceWasDisconnectedNotification
                               object:nil
                               queue:nil
                               usingBlock:^(NSNotification *){
        qDebug()<<"remove"<<QCameraInfo::availableCameras().size();
    }];
}

void free()
{
    if (attachHandle) {
        [[NSNotificationCenter defaultCenter] removeObserver:attachHandle];
    }
    if (detachHandle) {
        [[NSNotificationCenter defaultCenter] removeObserver:detachHandle];
    }
}

代码链接:

https://github.com/gongjianbo/MyTestCode/tree/master/Qt/DeviceHotplug_Mac

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/129780213
今日推荐