14、USB音频路由:USB音频设备类支持、USB音频设备枚举与路由、USB音频延迟问题处理
USB音频,说实话,是Android音频系统里最让人又爱又恨的一块。
爱它,是因为它让外接声卡、专业DAC、甚至车载系统变得异常灵活。恨它,是因为延迟和兼容性问题,经常让人抓狂。我这些年调试过的USB音频问题,少说也有几十个了,从手机OTG接解码器,到车机USB播放,踩过的坑真不少。
这一章,我们就来把USB音频路由的底裤扒干净。
14.1 USB音频设备类支持
Android对USB音频的支持,核心就是USB Audio Device Class (UAC)协议。说白了,就是USB组织定义的一套标准,让音频设备可以即插即用,不用装驱动。
目前主流的是UAC 1.0和UAC 2.0。我个人的经验是:
- UAC 1.0:兼容性最好,几乎所有设备都支持。但带宽有限,最高只能到96kHz/24bit,通道数也少。
- UAC 2.0:带宽大,支持到384kHz/32bit,多通道。但Android原生支持是从Android 10才开始完善的,之前都是各家厂商自己加补丁。
Android框架里,USB音频的接入点主要在AudioFlinger和AudioPolicyService。当USB设备插入时,系统会通过UsbManager和AudioService协同工作,最终在AudioPolicyManager里创建一个新的音频设备节点。
核心要点:Android把USB音频设备当作一个独立的audio_source或audio_sink来处理。它和内置扬声器、蓝牙耳机是平级的,只是路由策略不同。
14.2 USB音频设备枚举与路由
设备枚举,说白了就是系统怎么发现并识别这个USB音频设备。
流程大致是这样的:
- USB设备插入,
UsbHostManager收到内核通知。 - 系统解析设备的描述符,判断是不是音频类设备。
- 如果是,
AudioService会调用AudioSystem::setDeviceConnectionState,通知音频框架有新设备上线。 AudioPolicyManager根据策略,决定是否把这个设备设为默认输出或输入。
这里有个关键点:路由策略。Android默认的策略是:
- 如果插入了USB耳机/声卡,优先使用USB设备。
- 如果USB设备被拔出,自动切回内置扬声器或耳机。
- 如果同时插了USB设备和3.5mm耳机,系统会按策略选择,通常是USB优先。
我曾经遇到过一个项目,客户要求USB声卡插入后,只作为录音设备,播放还是走内置扬声器。这就得改AudioPolicyManager的规则了。说白了,路由策略不是死的,是可以定制的。
调试技巧:用dumpsys audio可以查看当前所有音频设备的连接状态。我经常用这个命令来确认USB设备是否被正确识别和路由。
14.3 USB音频延迟问题处理
嗯,说到延迟,这是USB音频最头疼的问题。
为什么USB音频延迟高?原因有几个:
- USB传输机制:USB是轮询总线,不是实时中断。音频数据要等下一个USB帧才能传输,这本身就引入了延迟。
- 缓冲区大小:Android默认的USB音频缓冲区比较大,为了稳定性牺牲了延迟。我见过有些设备缓冲区高达256帧,延迟直奔50ms以上。
- 路径长:音频数据从APP到USB设备,要经过AudioFlinger、HAL、USB驱动,每一层都有缓冲。
怎么优化?我个人总结了几个方向:
14.3.1 调整缓冲区大小
在audio_hw_usb.c(HAL层)里,可以修改period_size和buffer_size。比如:
// 默认可能是256帧,尝试改为128或64
#define PERIOD_SIZE 128
#define BUFFER_SIZE (PERIOD_SIZE * 4)
但注意,缓冲区越小,CPU占用越高,容易出现Xrun(欠载或溢出)。我建议从128帧开始试,不要一上来就搞64。
14.3.2 使用UAC 2.0的异步模式
UAC 2.0支持异步传输,设备端可以主动请求数据,而不是等主机轮询。这能显著降低延迟。Android从10开始对UAC 2.0异步模式支持得不错,但需要设备端也支持。
14.3.3 绕过AudioFlinger直接走USB
对于极低延迟场景(比如专业音频APP),可以使用AAudio或Oboe库,它们支持直接与USB设备通信,绕过AudioFlinger的混音和重采样。我做过一个项目,用AAudio的MMAP模式,把USB音频延迟从40ms降到了10ms以内。
注意:绕过AudioFlinger意味着你失去了系统级的混音能力。如果APP独占USB设备,其他APP的声音就听不到了。这在某些场景下可以接受,但做产品时要考虑清楚。
14.4 知识体系:USB音频路由核心逻辑
下面这张图,是我根据多年经验总结的USB音频路由核心流程。你看完应该能对整个链路有个清晰的印象。
14.5 实战避坑指南
最后,分享几个我踩过的坑,希望能帮你少走弯路。
- USB设备拔出时崩溃:我曾经遇到过,USB声卡突然拔出,AudioFlinger还在往里面写数据,直接导致crash。解决方案是在HAL层加一个
standby回调,拔出时立即停止数据流。 - 采样率不匹配:有些USB设备只支持44.1kHz,但APP输出48kHz。Android默认会重采样,但重采样算法质量参差不齐。我建议在
AudioPolicyManager里强制匹配设备支持的采样率。 - 多通道映射错误:USB声卡有8个通道,但Android只用了前2个。这是因为
audio_hw_usb.c里默认只映射了立体声。需要修改通道掩码配置。
我的习惯:每次调试USB音频问题,我都会先抓一份dumpsys audio和dmesg日志。这两个东西能告诉你90%的问题原因。剩下的10%,嗯,那就得靠经验和运气了。
USB音频路由,说白了就是一套「发现-注册-路由-优化」的流程。只要把每个环节的机制搞清楚了,遇到问题就不会慌。你想想看,是不是这个道理?