实现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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐