实现android系统通过usb麦克风采集声音功能,能够兼容多款anroid设备。

设想方案有两个:
1.采用通过libusb库,直接访问usb驱动,分析usb协议中的音频数据。
2.通过tinyalsa访问音频设备的pcm节点,通过节点直接获取音频数据。

因第二种方式音频节点id并不可控,并不能适配多款android设备,遂采用第一种方式。

通过代码调用libusb库,通过usb驱动获取数据,然后封装成jni库,供apk调用。

背景知识

每一个usb设备都有自己固定的VID和PID地址,根据这个地址可以寻找到指定的usb设备。

usb有config,然后下面有多个interface,interface下面有多个endpoint。根据interface的class和subclass值可以区分interface类型,比如video的class值是14,audio的class值是1等,根据这个可以识别复合设备的interface。然后每个interface下面有多个endpoint,endpoint存在address,这个是数据传输的通道。每个endpoint存在不同的数据格式,比如我手上的这个usb麦克风,每个endpoint对应一种格式,比如双通道/16位/48K。但也有一个endpoint对应多种格式的。

usb的传输类型有四种,分别是控制传输(Control Transfer)、中断传输(Interrupt Transfer)、批量传输(Bulk Transfer)、同步传输(Isochronous Transfer)。音频设备只用到控制传输和同步传输。

代码

1.初始化

先进行init,然后根据vid、pid查找usb设备,进行open,然后因为要使用无驱设计,需要将音频alsa和usb驱动进行解绑。最后扫描所有config,根据所需要的class找到所需的interface,进行选择设置。

int UsbAudio::open(int vid, int pid, int fd, const char *usbfs){    int r;                         //for return values      ssize_t cnt;                   //holding number of devices in list      printf("start open %d\n", errno);    if (mUsbFs)        free(mUsbFs);    mUsbFs = strdup(usbfs);    printf("before 11111 errno:%d\n", errno);    r = libusb_init2(&ctx, usbfs);         //initialize a library session      if(r < 0) {          printf("Init Error \n"); //there was an error          return -1;      }      usb_dev = libusb_find_device(ctx, vid, pid, NULL, fd);    if (usb_dev) {        libusb_set_device_fd(usb_dev, fd);  // assign fd to libusb_device for non-rooted Android devices        libusb_ref_device(usb_dev);    }    printf("before 444444 errno:%d\n", errno);    r = libusb_open(usb_dev, &dev_handle);    if(r != LIBUSB_SUCCESS)    {        printf("open device err %d\n", errno);        return -1;    }    //libusb_reset_device(dev_handle);    printf("before scan_audio_interface errno:%d\n", errno);    if (scan_audio_interface(usb_dev) < 0)    {        printf("scan_audio_interface err: errno:%d\n", errno);        cancel();        return -1;    }    printf("before interface_claim_if errno:%d\n", errno);    interface_claim_if(dev_handle, interfaceNumber);}int UsbAudio::interface_claim_if(libusb_device_handle *dev, int interface_number){    int r = 0;    r = libusb_kernel_driver_active(dev, interface_number);    printf("libusb_kernel_driver_active2 %d\n", r);      if(r == 1)     { //find out if kernel driver is attached          printf("Kernel Driver Active\n");          if(libusb_detach_kernel_driver(dev, interface_number) == 0) //detach it              printf("Kernel Driver Detached!\n");      }      printf("kernel detach errno:%d\n", errno);    r = libusb_claim_interface(dev, interface_number);            //claim interface 0 (the first) of device (mine had jsut 1)      if(r != 0) {          printf("Cannot Claim Interface\n");          return 1;      }      printf("claim_interface errno:%d\n", errno);    return 0;}

2.配置

设置相关属性,如采样率,interface下面的AlternateSetting等。

    ret = libusb_set_interface_alt_setting(dev_handle, interfaceNumber, 0);    if (ret != 0) {        printf("libusb_set_interface_alt_setting failed: %d: %s\n", interfaceNumber, libusb_error_name(ret));        return -1;    }    printf("Select the altsetting, interfaceNumber:%d, altsetting:%d\n", interfaceNumber, AlternateSetting);    ret = libusb_set_interface_alt_setting(dev_handle, interfaceNumber, AlternateSetting);    if (ret != 0) {        printf("libusb_set_interface_alt_setting failed: %d, %d: %s\n", interfaceNumber,         AlternateSetting, libusb_error_name(ret));        return -1;    }    ret = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT,                             0x01, 0x0100, EndpointAddress,                             rate, sizeof(rate), 0);    if (ret == sizeof(rate))     {        printf("set mic config success:0x%x:0x%x:0x%x\n",                         rate[0], rate[1], rate[2]);    }     else     {        printf("set mic config fail %d\n", ret);        return ret;    }

这些相关参数,可以根据uac协议解析判断,这里直接是抓包获取的参数,然后直接设置的。windows抓包可以用Bus Hound工具,linux驱动抓包工具可以使用usbmon。

  12.0  CTL    01 0b 00 00  03 00 00 00  SET INTERFACE         1963.1.0          12.0  CTL    01 0b 04 00  03 00 00 00  SET INTERFACE         1964.1.0          12.0  CTL    22 01 00 01  82 00 03 00  SET CUR               1965.1.0          12.0  OUT    80 bb 00                  ...                   1965.2.0          12.2  ISOC   00 00 00 00  00 00 00 00  ........              1966.1.0                       00 00 00 00  00 00 00 00  ........              1966.1.8                       00 00 00 00  00 00 00 00  ........              1966.1.16                      00 00 00 00  00 00 00 00  ........              1966.1.24         12.2  ISOC   55 00 55 00  5c 00 5c 00  U.U.\.\.              1967.1.0                       5c 00 5c 00  5f 00 5f 00  \.\._._.              1967.1.8                       5c 00 5c 00  58 00 58 00  \.\.X.X.              1967.1.16                      61 00 61 00  65 00 65 00  a.a.e.e.              1967.1.24         12.2  ISOC   d6 ff d6 ff  df ff df ff  ........              1968.1.0                       e7 ff e7 ff  ed ff ed ff  ........              1968.1.8                       e7 ff e7 ff  e9 ff e9 ff  ........              1968.1.16                      e2 ff e2 ff  e3 ff e3 ff  ........              1968.1.24       

3.获取数据

数据通过tranfers进行传输,先通过libusb_fill_iso_transfer进行填充,其中endpointaddress即之前config扫描获取的,callback函数即是数据回调接口,然后一次libusb_submit_transfer对应一次回调,数据即在回调接口中获取。同时要创建libusb_handle_events处理的线程,这个线程是在后台调度,保证callback回调的。

    endpoint_bytes_per_packet = PackSize;    packets_per_transfer = PackNum;    total_transfer_size = PackSize*PackNum;    printf("Set up the transfers\n");    printf("before fill EndpointAddress:%d, per_packet:%d, packets:%d, total_transfer_size:%d\n",                        EndpointAddress, endpoint_bytes_per_packet, packets_per_transfer, total_transfer_size);    for (transfer_id = 0; transfer_id < LIBUAC_NUM_TRANSFER_BUFS; ++transfer_id)     {        printf("fill transfer_id:%d\n", transfer_id);        transfer = libusb_alloc_transfer(packets_per_transfer);        transfers[transfer_id] = transfer;        transfer_bufs[transfer_id] = (unsigned char *)malloc(total_transfer_size);        memset(transfer_bufs[transfer_id], 0, total_transfer_size);        libusb_fill_iso_transfer(transfer, dev_handle,            EndpointAddress,            transfer_bufs[transfer_id], total_transfer_size,            packets_per_transfer, _uac_stream_callback,            (void *)NULL, 1000);        libusb_set_iso_packet_lengths(transfer, endpoint_bytes_per_packet);    }    printf("before submit errno:%d\n", errno);    for (transfer_id = 0; transfer_id < LIBUAC_NUM_TRANSFER_BUFS; transfer_id++) {        printf("submit transfer_id:%d\n", transfer_id);        ret = libusb_submit_transfer(transfers[transfer_id]);        if (ret != 0) {            printf("libusb_submit_transfer failed: %d, errno:%d\n", ret, errno);            break;        }        printf("submit transfer_id:%d finish\n", transfer_id);    }    printf("after submit errno:%d\n", errno);    thread_running = 1;    //pthread_create(&user_thread, NULL, fill_user_frame, NULL);    pthread_create(&event_thread, NULL, _uac_handle_events, (void*) ctx);
void *_uac_handle_events(void *args){    libusb_context *handle_ctx = (libusb_context *)args;    printf("%s start\n", __func__);    while(UsbAudio::thread_running)    {        if (libusb_handle_events(handle_ctx) != LIBUSB_SUCCESS) {            printf("libusb_handle_events err\n");            break;        }    }    printf("libusb_handle_events exit\n");}
void _uac_stream_callback(struct libusb_transfer *transfer) {    //printf("do callback\n");    switch (transfer->status)     {        case LIBUSB_TRANSFER_COMPLETED:            if (transfer->num_iso_packets) {                /* This is an isochronous mode transfer, so each packet has a payload transfer */                _uac_process_payload_iso(transfer);            }            break;        case LIBUSB_TRANSFER_NO_DEVICE:            UsbAudio::running = 0;  // this needs for unexpected disconnect of cable otherwise hangup            // pass through to following lines        case LIBUSB_TRANSFER_CANCELLED:        case LIBUSB_TRANSFER_ERROR:            break;        case LIBUSB_TRANSFER_TIMED_OUT:        case LIBUSB_TRANSFER_STALL:        case LIBUSB_TRANSFER_OVERFLOW:            break;    }    if (UsbAudio::thread_running)     {        //printf("libusb_submit_transfer next.\n");        if (libusb_submit_transfer(transfer) < 0) {            printf("libusb_submit_transfer err.\n");        }    } }
void _uac_process_payload_iso(struct libusb_transfer *transfer) {    /* per packet */    unsigned char *pktbuf;    size_t header_len;    unsigned char header_info;    struct libusb_iso_packet_descriptor *pkt;    int packet_id;    unsigned char* recv = (unsigned char*)malloc(PACKET_SIZE*NUM_PACKETS);//(PACKET_SIZE * transfer->num_iso_packets);    unsigned char* recv_next = UsbAudio::holdbuf;    int len = 0;    if (transfer->type != LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)     {        printf("not isoc packet\n");        return;    }    //printf("record pcm,isonum %d, len %d, actlen %d\n", transfer->num_iso_packets,transfer->length,transfer->actual_length);    for (packet_id = 0; packet_id < transfer->num_iso_packets; ++packet_id) {        pkt = &transfer->iso_packet_desc[packet_id];        if (pkt->status != LIBUSB_TRANSFER_COMPLETED) {            printf("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);            continue;        }        pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id);        if(pktbuf == NULL)        {            printf("receive pktbuf null\n");        }        memcpy(recv_next, pktbuf, pkt->length);        recv_next += pkt->length;        len += pkt->length;    }   // for    //在这里进行数据保存和拷贝    if (fwrite(recv, 1, len, pFile) < 0) {        perror("Unable to write to descriptor");    }    free(recv);    if (len > UsbAudio::PackSize * transfer->num_iso_packets) {        printf("Error: incoming transfer had more data than we thought.\n");        return;    }}

另外需要一个注意的地方是endpoint_bytes_per_packet是每个包长,该值必须比endpoint支持的最大包长要小。packets_per_transfer是包数,这个值其实另有含义,同时也是采样延时的时间。比如endpoint_bytes_per_packet设置为384,而packets_per_transfer设置为10,那么callback的时间就是10ms后返回3840byte数据。

总结

该程序其实是简单的获取指定usb麦克设备数据的。
其实可以做的更有兼容一些,比如扫描config/interface Descriptor,可以获取相关设备描述,区分是否存在音频设备。根据UAC相关协议进行封装,可以获取interface和endpoint的usb协议extra扩展部分,根据该部分信息,解析可以获取设备支持配置、endpoint长度地址等。

同类项目,针对usbcamera相关的无驱设计,可以参考git上的开源项目。该项目将UVC协议进行了封装。
UVCCamera

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. Android(安卓)设备上实现串口的移植
  5. Android平台基于Pull方式对XML文件解析及写入
  6. 美军方拨款 提升Android系统安全性能
  7. Android之传感器系统(Gsensor) .
  8. Android(安卓)定位服务
  9. Android(安卓)MVP模式实践

随机推荐

  1. 在定制android设备时,刷机后不全屏显示或
  2. android调节音量——AudioManager的应用
  3. Android开发笔记(八十三)多语言支持
  4. boot.img的解包与打包
  5. Android(安卓)炫酷的多重水波纹 MultiWav
  6. 《疯狂Android讲义》
  7. Android有未接来电后处理(判断未接来电)
  8. 解决android sdk无法更新 更新慢的问题
  9. Android(安卓)ListView滑动过程中图片显
  10. 解读Android之Intents和Intent Filters