UAC介绍及实现
一、UAC简介
1.UAC是什么?
UAC是USB Audio Class的缩写,有时也叫UAD,UAD是USB Audio Device的缩写。
它基于libusb,实现对外接音频操作。通过UAC,可以实现实时获取音频设备的音频数据,并且通过UAC实现操控设备音量,采样率,等参数。UAC是基于libusb,实现对外接音频操作,从用户功能来说,主要包括USB麦克风、USB声卡和其它音频设备的功能控制和接口标准。
2.UAC设备类
根据USB设备类定义,UAC设备的类号为1
/** Audio */
USB_DEVICE_CLASS_AUDIO 0x01//1
/** Video */
USB_DEVICE_CLASS_VIDEO 0x0e,//14
3.USB音频子类
USB音频类定义在接口层,而USB音频类又分为不同的子类(SubClass)以便于进一步的细节枚举和设置。所有的USB音频功能都被包括在USB音频类的子类中。USB定义了3种不同的音频子类:
① AudioControl Interface Subclass 音频控制接口子类
② AudioStreaming Interface Subclass 音频流接口子类
③ MIDIStreaming Interface Subclass MIDI流接口子类
二、libusb是什么
libusb是一个C语言编写的库,可以供上层的应用来调用,和连接在笔记本上的USB设备进行通信。易于移植,而且有对应的API的文档,可以用于Linux, OS X, Windows, Android等系统。而且支持USB 1.0到3.1的规范。
它是基于USB协议开发的一个封装包,包含了通过USB协议对USB设备进行读/写操作,如:我们常见的UVC,就是通过libusb实现获取设备的实时视频数据,它仍然符合USB协议。
1. USB标准描述符
- 在USB设备的逻辑组织中,包含devices、config、interface和endpoint4个层次。
- 根据usb描述符相关知识得知,usb有config,然后下面有多个interface,interface下面有多个endpoint。根据interface的class和subclass值可以区分interface类型,比如video的class值是14,audio的class值是1等,根据这个可以识别复合设备的interface。然后每个interface下面有多个endpoint,endpoint存在address,这个是数据传输的通道。每个endpoint存在不同的数据格式,比如我在项目中使用的多个usb麦克风,有的MIC每个endpoint对应一种格式,比如双通道/16位/48KHZ。但也有一个endpoint对应多种格式的
2.USB设备描述符(Device Descriptor)
设备描述符是USB设备的第一个描述符,每个USB设备都得具有设备描述符,且只能拥有一个。并且在设备描述符中,包含了该设备的idVendor、idProduct、iSerialNumber,通过这些参数可以在使用多个USB设备时找到指定设备。
Offset | Field | Size | Value | Description |
---|---|---|---|---|
0 | bLength | 1 | Number | 以字节为单位的描述符大小 |
1 | bDescriptorType | 1 | Constant | 设备描述符类型 |
2 | bcdUSB | 2 | BCD | USB规范版本号 |
4 | bDeviceClass | 1 | Class | 类码 |
5 | bDeviceSubClass | 1 | SubClass | 子类码 |
6 | bDeviceProtocol | 1 | Protocol | 协议码 |
7 | bMaxPacketSize0 | 1 | Number | 端点0的最大包大小 |
8 | idVendor | 2 | ID | 厂商ID |
10 | idProduct | 2 | ID | 产品ID |
12 | bcdDevice | 2 | BCD | 设备版本号 |
14 | iManufacturer | 1 | Index | 制造商字符串描述符索引 |
15 | iProduct | 1 | Index | 产品的字符串描述符索引 |
16 | iSerialNumber | 1 | Index | 设备序列号的字符串描述符索引 |
17 | bNumConfigurations | 1 | Number | 可能的配置数目 |
3.USB配置描述符(Configuration Descriptor)
配置描述符(Configuration Descriptor)说明了一个特定配置的相关信息。取得设备描述符(Device Descriptor)后,主机就可以继续去获取设备的配置、接口和端点描述符。当主机请求配置描述符(Configuration Descriptor)时,返回的是所有相关的接口和端点描述符。
Offset | Field | Size | Value | Description |
---|---|---|---|---|
0 | bLength | 1 | Number | 以字节为单位的描述符大小 |
1 | bDescriptorType | 1 | Constant | 配置描述符类型 |
2 | wTotalLength | 2 | Number | 配置返回的数据总长度 |
4 | bNumInterfaces | 1 | Number | 配置支持的接口数量 |
5 | bConfigurationValue | 1 | Number | Get Configuration 和Set Configuration请求的配置值 |
6 | iConfiguration | 1 | Index | 字符串描述符索引 |
7 | bmAttributes | 1 | Bitmap | 配置特性 |
8 | bMaxPower | 1 | mA | 设备从总线获取的最大功耗 |
4.USB接口描述符(Interface Descriptor)
接口描述符(Interface Descriptor)描述了配置中一个特定的接口。配置提供了一个或多个接口,每个接口都含有类(Class),子类(SubClass)和协议(Protocol)的信息,以及接口所使用的端点(Endpoint)数目。
Offset | Field | Size | Value | Description |
---|---|---|---|---|
0 | bLength | 1 | Number | 以字节为单位的描述符大小 |
1 | bDescriptorType | 1 | Constant | 接口描述符类型 |
2 | bInterfaceNumber | 1 | Number | 接口的编号 |
3 | bAlternateSetting | 1 | Number | 用来确认bInterfaceNumber的替代设置的编号 |
4 | bNumEndpoints | 1 | Number | 接口使用的端点数量 |
5 | bInterfaceClass | 1 | Class | 类码 |
6 | bInterfaceSubClass | 1 | SubClass | 子类码 |
7 | bInterfaceProtocol | 1 | Protocol | 协议码 |
8 | iInterface | 1 | Index | 接口字符串描述符的索引 |
5.USB端点描述符(Endpoint Descriptor)
端点描述符 (Endpoint Descriptor)描述了USB规范定义的端点信息,包含有端点的带宽等信息。每一个端点都有自己的端点描述符。主机端获取端点描述符 (Endpoint Descriptor),总是作为配置描述符(Configuration Descriptor)的一部分返回,不能直接用Get Descriptor或者Set Descriptor请求访问。
Offset | Field | Size | Value | Description |
---|---|---|---|---|
0 | bLength | 1 | Number | 以字节为单位的描述符大小 |
1 | bDescriptorType | 1 | Constant | 端点描述符类型 |
2 | bEndpointAddress | 1 | Endpoint | 设备的端点地址 |
3 | bmAttributes | 1 | Bitmap | 端点的属性 |
4 | wMaxPacketSize | 2 | Number | 端点能发送或接收的最大数据包大小 |
6 | bInterval | 1 | Number | 查询端点进行数据传输的间隔 |
6.如何获取这些数据呢?
最简单的方式是在Linux系统中使用命令:lsusb
在Linux上,我们可以使用lsusb来列出USB设备和它的属性
如图:
Bus 002 : 指明设备连接到哪(哪条总线)
Device 003: 表明这是连接到总线上的第3台设备
ID : 设备的ID (046d 就是idVendor ,085e就是 idProduct)
**Logitech, Inc. ** :生产商名字和设备名
我们同样可以看到电脑系统中同时使用了Linux Foundation 2.0 root hub和Linux Foundation 3.0 root hub驱动
-
获取USB详细信息,命令:lsusb -d 046d:085e -v
Bus 002 Device 003: ID 046d:085e Logitech, Inc. Couldn't open device, some information will be missing Device Descriptor: bcdUSB 3.10 bDeviceClass 239 Miscellaneous Device bDeviceProtocol 1 Interface Association idVendor 0x046d Logitech, Inc. idProduct 0x085e bcdDevice 3.17 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 3476 bNumInterfaces 6 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 224mA Interface Descriptor: bInterfaceClass 14 Video bInterfaceSubClass 2 Video Streaming VideoStreaming Interface Descriptor: bLength 54 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) wWidth 4096 wHeight 2160 dwMinBitRate 707788800 dwMaxBitRate 4246732800 dwMaxVideoFrameBufferSize 17694720 dwDefaultFrameInterval 333333 bFrameIntervalType 7 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 416666 dwFrameInterval( 2) 500000 dwFrameInterval( 3) 666666 dwFrameInterval( 4) 1000000 dwFrameInterval( 5) 1333333 dwFrameInterval( 6) 2000000 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 2 bNumEndpoints 1 bInterfaceClass 1 Audio bInterfaceSubClass 2 Streaming AudioStreaming Interface Descriptor: bLength 11 bDescriptorType 36 bNrChannels 2 bSubframeSize 2 bBitResolution 16 bSamFreqType 1 Discrete tSamFreq[ 0] 24000 Endpoint Descriptor: bLength 9 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN wMaxPacketSize 0x0064 1x 100 bytes Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 3 bNumEndpoints 1 bInterfaceClass 1 Audio bInterfaceSubClass 2 Streaming AudioStreaming Interface Descriptor: bLength 11 bDescriptorType 36 tSamFreq[ 0] 32000 Endpoint Descriptor: bLength 9 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 4 bNumEndpoints 1 bInterfaceClass 1 Audio bInterfaceSubClass 2 Streaming AudioStreaming Interface Descriptor: bLength 11 bDescriptorType 36 tSamFreq[ 0] 48000 Endpoint Descriptor: bLength 9 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN wMaxPacketSize 0x00c4 1x 196 bytes
三、基于libusb实现UAC
1.时序图
每一个usb设备都有自己固定的VID与PID,根据这个地址可以找到指定USB设备。先进行init libusb,再根据vid、pid找到设备,接着打开这个设备,最后扫描所有的config,再根据需求找到对应的bInterfaceSubClass,进行选择设置,如下面的时序图:
2.UAC重点代码讲解
- 初始化
根据USB设备的vid、pid找到指定设备,并配置该设备的采样率,以及获取设备数据
int USBAudio::initAudio(int vid, int pid, int busnum, int devaddr,
int fd, const char *usbfs) {
int ret = 0;
ret = libusb_init2(&uac_ctx, usbfs); //initialize a library session
uac_dev = libusb_get_device_with_fd(uac_ctx, vid, pid, NULL, fd, busnum, devaddr);
ret = libusb_open(uac_dev, &uac_devh);
//scan interface
ret = scan_audio_interface(uac_dev);
//claim_interface and set_interface_alt_setting
ret = interface_claim_if(uac_devh);
//Set sample rate
ret = set_sample_rate_v1(sample_rate);
//创建音频回调线程,设置音频回调函数
ret = fill_iso_transfer();
return ret;
}
-
通过libusb_control_transfer设置和获取采样率
libusb_control_transfer 给usb设备发送控制指令
//设置数据包的请求类型字段 int USB_REQ_CS_ENDPOINT_SET = // 0x22 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT; int USB_REQ_CS_ENDPOINT_GET = // 0xa2 LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT; //设置数据包的请求字段 int UAC_SET_CUR = 0x01; int UAC_GET_CUR = 0x81; int rate = 48000; int USBAudio::set_sample_rate_v1(int rate) { unsigned char data[3]; int ret, crate; data[0] = (rate & 0xff); data[1] = (rate >> 8); data[2] = (rate >> 16); //设置采样率 ret = libusb_control_transfer(uac_devh, USB_REQ_CS_ENDPOINT_SET, UAC_SET_CUR, 0x0100, _speakerEndpoint, data, sizeof(data), 500); //获取当前采样率 ret = libusb_control_transfer(uac_devh, USB_REQ_CS_ENDPOINT_GET, UAC_GET_CUR, 0x0100, _speakerEndpoint, data, sizeof(data), 500); crate = data[0] | (data[1] << 8) | (data[2] << 16); return 0; }
-
获取数据
数据通过tranfers进行传输,先通过libusb_fill_iso_transfer进行填充,其中endpointaddress即之前config扫描获取的,callback函数即是数据回调接口,然后一次libusb_submit_transfer对应一次回调,数据即在回调接口中获取。同时要创建libusb_handle_events处理的线程,这个线程是在后台调度,保证callback回调的。
//创建音频回调线程,设置音频回调函数
int USBAudio::fill_iso_transfer() {
for (transfer_id = 0; transfer_id < NUM_TRANSFERS; ++transfer_id) {
transfer = (struct libusb_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);
//将uac_devh、_speakerEndpoint、_speakerEndpoint等参数赋值给libusb_transfer
libusb_fill_iso_transfer(transfer, uac_devh,
_speakerEndpoint,
transfer_bufs[transfer_id], total_transfer_size,
packets_per_transfer, _uac_stream_callback,
(void *) uac_handler, 0);
libusb_set_iso_packet_lengths(transfer, endpoint_bytes_per_packet);
}
for (transfer_id = 0; transfer_id < NUM_TRANSFERS; transfer_id++) {
//将transfer传递到。此函数将触发 USB 传输,然后立即返回
r = libusb_submit_transfer(transfers[transfer_id]);
}
return r;
}
//音频数据回调
void _uac_process_payload_iso(struct libusb_transfer *transfer, uac_stream_handler_t *uac_handler) {
//杂音问题
for (int j = 0; j < transfer->num_iso_packets; ++j) {
struct libusb_iso_packet_descriptor *pack = &transfer->iso_packet_desc[j];
maxLen += pack->actual_length;
}
jbyteArray audioByteArray = (*env)->NewByteArray(env, maxLen);
for (i = 0; i < transfer->num_iso_packets; i++) {
struct libusb_iso_packet_descriptor *pack = &transfer->iso_packet_desc[i];
const uint8_t *data = libusb_get_iso_packet_buffer_simple(transfer, i);
(*env)->SetByteArrayRegion(env, audioByteArray, len, pack->actual_length, (jbyte *) data);
len += pack->actual_length;
}
// Call write()
(*env)->CallVoidMethod(env, object->audioObject,
object->pcmData, audioByteArray, maxLen);
(*env)->DeleteLocalRef(env, audioByteArray);
}
3.问题总结
- 使用libusb_control_transfer,报错LIBUSB_ERROR_PIPE()
参数设置错误,如:bmRequestType:(LIBUSB_ENDPOINT_OUT |LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT),wIndex:(EndpointAddress),并且部分摄像头必须要求设置采样率
- 设置了采样率,但是部分设备仍然无法获取到音频数据
当前传过去的采样率在设备当前选择的interface中不存在!
- 获取的声音存在杂音
部分USB设备通过libusb_get_iso_packet_buffer_simple获取的音频数据长度是变化的,导致回传回去的数据与USB获取的音频数据长度不一致
## 四、UAC流程图
对UAC的相关知识已经讲解完了,如果对这个有兴趣的可以根据下面的相关资料去学习,对代码有需求的我放在下面我的git仓库中,最后我们再来看一下UAC项目流程图