修改UAC配置参数后无法正常通信

如果修改了UAC的配置后出现UAC无法正常通信的问题,可以通过修改设备的idVendor和idProduct来解决,只有设备的idVendor和idProduct发生改变Windows才会重新读取设备的配置描述符。

Kernel 5.4内核配置某些参数后UAC通信异常

例如在44100采样率、4channel、32bit参数下Device到Host端通信异常,而Host端到Device端通信正常。 问题的原因是由于Linux的UAC驱动计算的每次传输的usb包大小不正确,导致USB传输格式不匹配出现了通信异常。详细原因如下:

  • 该参数下每秒钟的数据量为44100 * 4 * 4 = 705600
  • USB每秒传输一千个包,则每个包的大小为 705600 / 1000 = 705.6
  • Linux下UAC驱动的做法是每次传输705字节数据,当余数足够多的时候传输705+16=721字节,然后依次循环下去。但是这种方式存在一个问题就是705不是frames大小的整数倍,即705 / (4 * 4) = 44.0625,那么USB每次传输的数据不能完全送到声卡,要等下一次的数据到来后拼成一个完整的frame才能送到声卡,这样显然是非常麻烦的,且会造成更大的延迟
  • Windows的做法是每次传输704字节,然后剩余的数据积攒到一帧的数据量后下次传输704 + (4 * 4)= 720字节
  • 可以看到Linux和Windows每次传输和接收的大小都不一样,导致了两则无法正常通信,显然Linux的计算方法是错误的,Linux驱动中也没有自己拼接数据的逻辑,这个就是Linux下计算usb包大小的一个Bug。

Linux上游社区已经发现并解决了这个Bug,最终计算到的大小与Windows的大小相同,计算方法如下:

\[ pkt\_size = frame\_size * \frac{sampling\_rate}{1000} \]

即如下代码:

1
pkt_size = frame_size * (sampling_rate / 1000) // 704 = 16 * (44100 / 1000) = 16 * 44

UAC1 无法支持48K、4channel、32bit问题

drivers/usb/gadget/function/u_uac1.h:

1
#define UAC1_OUT_EP_MAX_PACKET_SIZE	200

这里闲置了usb每个包的大小不能超过200字节,我们可以计算一下数据量:

对于48K、4channel、32bit每秒钟的数据量为:48000_4_4 = 768,000Byte

USB每秒发送1000次数据,每次最大3个数据包,则每个包大小需要768000 / 1000 / 3 = 256。这里的宏定义将每个包的大小限制在200了,则带宽不够,需要放开这个限制,将宏大小改为1023就可以了。

💡 驱动中为什么默认设置为200呢,其实是为了保证usb低速设备可以正常使用才设置这么低的,对于usb低速设备1.5Mbit/s = 1.5 * 1024 *1024 / 8 / 1000 = 196.如果usb支持全速或者高速可以将这个限制放开。

UAC2无法支持大于192K、4channel、32bit参数问题

drivers/usb/gadget/function/f_uac2.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static struct usb_endpoint_descriptor hs_epout_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
...
static struct usb_endpoint_descriptor hs_epin_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};

这里bInterval是控制UAC发包频率的,USB全速总线1ms发送一次数据,USB高速总线可以125us发送一次数据,这里的bInterval就是用来控制UAC多久发送一次数据的,如果bInterval=1,则UAC数据包的间隔就是\(125us * 2^{1-1}\)如果bInterval=4则UAC发送间隔为\(125us * 2^{4-1}=1ms\)。驱动中默认是1ms发送一次数据,则最大的数据量就是1000 * 1024 * 3 = 3072000 = 192K * 4channel * 4Byte。

要解决这个问题只需要将bInterval改为1即可。

💡 bInterval=1具有更低的延迟,更大的带宽,但是对于大部分场景没必要设置这么高的频率,1ms就可以满足要求。

各系统UAC音量调节支持情况

Windows10/11 Android 11(基于一加6) ios Macos
UAC1 Disable FU 通过调节音频数据幅值调节音量 通过调节音频数据幅值调节音量 不支持调节音量(音量滑块变灰不可拖动) 同ios(未做验证)
UAC1 Enable FU 支持控制音量;不支持同步设备音量 通过调节音频数据幅值调节音量 支持控制音量;支持同步设备音量 同ios(未做验证)
UAC2 Disable FU 通过调节音频数据幅值调节音量 通过调节音频数据幅值调节音量 不支持调节音量(音量滑块变灰不可拖动) 同ios(未做验证)
UAC2 Enable FU 支持控制音量;支持同步设备音量 通过调节音频数据幅值调节音量 支持控制音量;支持同步设备音量 同ios(未做验证)

💡 1. 网上查到的信息是Android 14支持Feature Unit,但是在我的一加6(Android11)上验证是不支持FU的 2. macos应该与ios一致,未作验证

关于windows系统UAC1对FU支持情况的疑问

Windows 的UAC2对Function的支持情况有明确说明:

UAC2的Feature Unit是明确支持Interrupt的。但是没有查到UAC1对Function的支持情况。实际测试发现Windows是可以正确识别Feature unit端点描述符的,如下:

这里面的信息与Spec里都能一一对应上,端点描述符没有任何问题,但是测试发现Windows不会去读取Interrupt端点数据,抓包发现没有任何该端点的读取操作,那么实测得到的UAC1对Feature支持情况是:

  • 支持:
    • GET CUR
    • SET CUR
    • GET RANGE
  • 不支持:
    • Interrupt