UAC介绍及实现

2 篇文章 2 订阅
2 篇文章 1 订阅

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标准描述符

  1. 在USB设备的逻辑组织中,包含devices、config、interface和endpoint4个层次。
  2. 根据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对应多种格式的
USB配置描述符
USB配置描述符
USB接口描述符
....
USB接口描述符
USB接口描述符
....
USB接口描述符
USB端点描述符
USB端点描述符
USB端点描述符
USB端点描述符
USB设备描述符

2.USB设备描述符(Device Descriptor)

设备描述符是USB设备的第一个描述符,每个USB设备都得具有设备描述符,且只能拥有一个。并且在设备描述符中,包含了该设备的idVendor、idProduct、iSerialNumber,通过这些参数可以在使用多个USB设备时找到指定设备。

OffsetFieldSizeValueDescription
0bLength1Number以字节为单位的描述符大小
1bDescriptorType1Constant设备描述符类型
2bcdUSB2BCDUSB规范版本号
4bDeviceClass1Class类码
5bDeviceSubClass1SubClass子类码
6bDeviceProtocol1Protocol协议码
7bMaxPacketSize01Number端点0的最大包大小
8idVendor2ID厂商ID
10idProduct2ID产品ID
12bcdDevice2BCD设备版本号
14iManufacturer1Index制造商字符串描述符索引
15iProduct1Index产品的字符串描述符索引
16iSerialNumber1Index设备序列号的字符串描述符索引
17bNumConfigurations1Number可能的配置数目

3.USB配置描述符(Configuration Descriptor)

配置描述符(Configuration Descriptor)说明了一个特定配置的相关信息。取得设备描述符(Device Descriptor)后,主机就可以继续去获取设备的配置、接口和端点描述符。当主机请求配置描述符(Configuration Descriptor)时,返回的是所有相关的接口和端点描述符。

OffsetFieldSizeValueDescription
0bLength1Number以字节为单位的描述符大小
1bDescriptorType1Constant配置描述符类型
2wTotalLength2Number配置返回的数据总长度
4bNumInterfaces1Number配置支持的接口数量
5bConfigurationValue1NumberGet Configuration 和Set Configuration请求的配置值
6iConfiguration1Index字符串描述符索引
7bmAttributes1Bitmap配置特性
8bMaxPower1mA设备从总线获取的最大功耗

4.USB接口描述符(Interface Descriptor)

接口描述符(Interface Descriptor)描述了配置中一个特定的接口。配置提供了一个或多个接口,每个接口都含有类(Class),子类(SubClass)和协议(Protocol)的信息,以及接口所使用的端点(Endpoint)数目。

OffsetFieldSizeValueDescription
0bLength1Number以字节为单位的描述符大小
1bDescriptorType1Constant接口描述符类型
2bInterfaceNumber1Number接口的编号
3bAlternateSetting1Number用来确认bInterfaceNumber的替代设置的编号
4bNumEndpoints1Number接口使用的端点数量
5bInterfaceClass1Class类码
6bInterfaceSubClass1SubClass子类码
7bInterfaceProtocol1Protocol协议码
8iInterface1Index接口字符串描述符的索引

5.USB端点描述符(Endpoint Descriptor)

端点描述符 (Endpoint Descriptor)描述了USB规范定义的端点信息,包含有端点的带宽等信息。每一个端点都有自己的端点描述符。主机端获取端点描述符 (Endpoint Descriptor),总是作为配置描述符(Configuration Descriptor)的一部分返回,不能直接用Get Descriptor或者Set Descriptor请求访问。

OffsetFieldSizeValueDescription
0bLength1Number以字节为单位的描述符大小
1bDescriptorType1Constant端点描述符类型
2bEndpointAddress1Endpoint设备的端点地址
3bmAttributes1Bitmap端点的属性
4wMaxPacketSize2Number端点能发送或接收的最大数据包大小
6bInterval1Number查询端点进行数据传输的间隔

6.如何获取这些数据呢?

最简单的方式是在Linux系统中使用命令:lsusb

在Linux上,我们可以使用lsusb来列出USB设备和它的属性

如图:lsusb

Bus 002 : 指明设备连接到哪(哪条总线)

Device 003: 表明这是连接到总线上的第3台设备

ID : 设备的ID (046d 就是idVendor ,085e就是 idProduct)

**Logitech, Inc. ** :生产商名字和设备名

我们同样可以看到电脑系统中同时使用了Linux Foundation 2.0 root hubLinux Foundation 3.0 root hub驱动

  1. 获取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,进行选择设置,如下面的时序图:

USBAudio时序图

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项目流程图

USBAudio流程图

五、相关资料

  • 9
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芝麻猪oo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值