Android libusb库使用总结

2,911 阅读6分钟

libusb介绍

libusb是一个跨平台的开源库,用于在用户空间进行USB设备的访问和控制。它提供了一组函数和接口,使开发人员能够直接与USB设备进行通信,而无需编写内核驱动程序。

libusb GIT仓库:

github.com/libusb/libu…

流程图

libusb的使用一般分为5个步骤

  • 初始化

  • 打开设备

  • 移除原驱动/claim接口

  • 传输

  • 关闭设备

标准实现流程图参考

在Android上,由于打开USB Device之前需要先申请权限,流程上一般为

差异主要在使用USBManager获取UsbDevice并鉴权后得到设备的FileDescriptor,传入JNI层进行libusb的初始化。

libusb在Android平台上初始化

获取UsbManager

// Initialize UsbManager
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

注册USB授权回调

创建usbReceiver

private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "usbReceiver action:" + action);
        if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if (device != null) {
                        createDevice(device);
                    }
                } else {
                    Log.d(TAG, "permission denied for device " + device);
                }
            }
        }
    }
};

注册回调

// Initialize the receiver for getting the device permission
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

检索设备

根据设备信息(vid,pid)查找要处理的设备

protected void checkUsbDevices() {
    PendingIntent permissionIntent;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE);
    } else {
        permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
    }

    HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
    for (UsbDevice device : deviceList.values()) {
        if (device.getVendorId() == VID) {
            if (usbManager.hasPermission(device)) {
            //有权限则直接执行Native层逻辑,插入usb设备后下次打开进入此流程
                createDevice(device);
            } else {
            //插入后首次使用需申请权限
                usbManager.requestPermission(device, permissionIntent);
            }
        }
    }
}

调用nativeCreate,执行Native层初始化逻辑

protected void createDevice(UsbDevice device) {
    UsbDeviceConnection connection = usbManager.openDevice(device);
    int fileDescriptor = connection.getFileDescriptor();

    Log.d(TAG, "vid:" + Integer.toHexString(device.getVendorId()));
    nativeCreate(device.getVendorId(), device.getProductId(), fileDescriptor);
}

JNI层进行libusb库的初始化

其中Android平台与标准流程差别主要在

  1. libusb_set_option(NULL,LIBUSB_OPTION_NO_DEVICE_DISCOVERY,NULL);跳过设备发现流程
  2. libusb_wrap_sys_device(ctx, (intptr_t) fileDescriptor, &devh);使用fileDescriptor进行libusb_device_handle结构体的初始化 libusb_wrap_sys_device用于将现有的系统设备(sysfs或devfs中的设备)包装为libusb设备对象。它允许开发人员在不通过USB设备扫描或枚举操作的情况下,将已知的系统设备与libusb库进行交互
  3. 后续获取config,查找业务需要的interface及endpoint与标准流程一致

这里以访问HID设备为例

static int test_libusb(jint vid, jint pid, int fileDescriptor) {
    LOGD("test_libusb");
    libusb_context *ctx = NULL;
    libusb_device_handle *devh = NULL;
    int r = 0;
    r = libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
    if (r != LIBUSB_SUCCESS) {
        LOGD("libusb_set_option failed: %d\n", r);
        return -1;
    }
    r = libusb_init(&ctx);
    if (r < 0) {
        LOGD("libusb_init failed: %d\n", r);
        return r;
    }
    r = libusb_wrap_sys_device(ctx, (intptr_t) fileDescriptor, &devh);
    if (r < 0) {
        LOGD("libusb_wrap_sys_device failed: %d\n", r);
        return r;
    } else if (devh == NULL) {
        LOGD("libusb_wrap_sys_device returned invalid handle\n");
        return r;
    }
    print_device(libusb_get_device(devh), devh);

    int err;
    int endpoint;
    int interface_num = 0;
    int found = 0;
    int transferred;
    int count = 0;
    int bufferLen = 128;
    unsigned char buffer[bufferLen];
    struct libusb_config_descriptor *config_desc;

    /* parse interface descriptor, find usb mouse */
    err = libusb_get_config_descriptor(libusb_get_device(devh), 0, &config_desc);
    if (err) {
        LOGD("could not get configuration descriptor\n");
        return err;
    }
    LOGD("libusb_get_config_descriptor() ok\n");

    for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) {
        const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];
        interface_num = intf_desc->bInterfaceNumber;
        if (intf_desc->bInterfaceClass == 3) {
            LOGD("find usb dev ok\n");
            for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) {
                if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||
                    (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {
                    /* 找到了输入的中断端点 */
                    LOGD("find in int endpoint\n");
                    endpoint = intf_desc->endpoint[ep].bEndpointAddress;
                    found = 1;
                    break;
                }
            }
        }
    }

    libusb_free_config_descriptor(config_desc);

    if (!found) {
        libusb_release_interface(devh, interface_num);
        libusb_close(devh);
        libusb_exit(NULL);
        return -1;
    }

    /* enable automatic attach/detach kernel driver on supported platforms in libusb */
    libusb_set_auto_detach_kernel_driver(devh, 1);

    /* claim interface */
    err = libusb_claim_interface(devh, interface_num);
    if (err) {
        LOGD("failed to libusb_claim_interface\n");
        exit(1);
    }
    LOGD("libusb_claim_interface ok\n");

    /* libusb_interrupt_transfer */
    while (1) {
        err = libusb_interrupt_transfer(devh, endpoint, buffer, bufferLen, &transferred, 5000);
        if (!err) {
            /* parser data */
            LOGD("%04d count: ", count++);
            LOGD("data: %s", buffer);
        } else if (err == LIBUSB_ERROR_TIMEOUT) {
            LOGD("libusb_interrupt_transfer timout\n");
        } else {
            LOGD("libusb_interrupt_transfer err : %d\n", err);
            //exit(1);
        }
    }

    libusb_close(devh);

    return r;
}

数据传输

数据传输分为同步和异步模式

同步传输

libusb_control_transfer写入控制指令
// 发送控制传输请求
    uint8_t bmRequestType = LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT;
    uint8_t bRequest = 0x01;
    uint16_t wValue = 0x1234;
    uint16_t wIndex = 0;
    unsigned char data[64];
    uint16_t wLength = sizeof(data);
    unsigned int timeout = 1000;
    int result = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex,
                                         data, wLength, timeout);
libusb_bulk_transfer批量传输
// 执行批量传输操作
    unsigned char in_data[64];
    unsigned char out_data[64];
    int length = sizeof(out_data);
    int actual_length;
    unsigned int timeout = 1000;
    // 向设备发送数据
    int result = libusb_bulk_transfer(dev_handle, BULK_ENDPOINT_OUT, out_data,
                                      length, &actual_length, timeout);
libusb_interrupt_transfer中断传输

中断传输一般用于小批量的和非连续的数据传输,通俗的来说就是用于数据量小的数据不连续的但实时性高的场合的一种传输方式,主要应用于人机交互设备(HID)中的USB鼠标和USB键盘等。

参数length表示"期望读到的数据最大长度", 实际读到的长度保存在transferred参数里

// 执行中断传输操作
    unsigned char in_data[64];
    unsigned char out_data[64];
    int length = sizeof(out_data);
    int actual_length;
    unsigned int timeout = 1000;
    // 向设备发送数据
    int result = libusb_interrupt_transfer(dev_handle, INTERRUPT_ENDPOINT_OUT, out_data,
                                           length, &actual_length, timeout);

异步传输

libusb_alloc_transfer分配libusb_transfer结构体
// 初始化异步传输结构,参数0代表iso_packets的数量,
// 对于控制传输、批量传输、中断传输,iso_packets参数需要设置为0
    struct libusb_transfer *transfer = libusb_alloc_transfer(0);
libusb_fill_bulk_transfer,libusb_fill_interrupt_transfer设置参数

如果你传入buffer参数,那么buffer的前面8字节会被当做"control setup packet"来解析,

buffer的最后2字节表示wLength,它也会被用来设置libusb_transfer::length

所以,建议使用流程如下:

  1. 分配buffer,这个buffer的前面8字节对应"control setup packet",后面的空间可以用来保存其他数据

  2. 设置"control setup packet",通过调用libusb_fill_control_setup()函数来设置

  3. 如果是要把数据发送给设备,把要发送的数据放在buffer的后面(从buffer[8]开始放)

  4. 调用libusb_fill_bulk_transfer

  5. 提交传输: 调用libusb_submit_transfer()

也可以让buffer参数为 NULL

这种情况下libusb_transfer::length就不会被设置,需要手工去设置libusb_transfer::buffer、libusb_transfer::length

// 设置传输的参数
    libusb_fill_bulk_transfer(transfer, dev_handle, BULK_ENDPOINT_IN,
        transfer_callback, NULL, 0);

libusb_fill_interrupt_transfer等逻辑与之基本相同

libusb_fill_iso_transfer

等时传输也有“同步传输”的叫法,一般用于要求数据连续、实时且数据量大的场合,其对传输延时十分敏感,类似用于USB摄像设备,USB语音设备等等。

等时传输是不保证数据100% 正确的。当数据错误时,并不进行重传操作

libusb_submit_transfer提交

该函数会启动传输并返回结果,传输数据通过上一步传入的callback方法回调得到

// 提交传输请求
    int result = libusb_submit_transfer(transfer);
libusb_handle_events处理pengding的事件

在调用libusb_handle_events时,它会处理挂起的USB事件,并驱动传输操作的进行。这包括完成已提交的传输、检测新的USB设备插拔事件等。函数会阻塞等待直到有事件发生或超时

while (!do_exit) {
   r = libusb_handle_events(NULL);
   if (r < 0)
      request_exit(2);
}
libusb_free_transfer

传输结束后通过libusb_free_transfer释放资源