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音频的接入点主要在AudioFlingerAudioPolicyService。当USB设备插入时,系统会通过UsbManagerAudioService协同工作,最终在AudioPolicyManager里创建一个新的音频设备节点。

核心要点:Android把USB音频设备当作一个独立的audio_sourceaudio_sink来处理。它和内置扬声器、蓝牙耳机是平级的,只是路由策略不同。

14.2 USB音频设备枚举与路由

设备枚举,说白了就是系统怎么发现并识别这个USB音频设备。

流程大致是这样的:

  1. USB设备插入,UsbHostManager收到内核通知。
  2. 系统解析设备的描述符,判断是不是音频类设备。
  3. 如果是,AudioService会调用AudioSystem::setDeviceConnectionState,通知音频框架有新设备上线。
  4. 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_sizebuffer_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),可以使用AAudioOboe库,它们支持直接与USB设备通信,绕过AudioFlinger的混音和重采样。我做过一个项目,用AAudio的MMAP模式,把USB音频延迟从40ms降到了10ms以内。

注意:绕过AudioFlinger意味着你失去了系统级的混音能力。如果APP独占USB设备,其他APP的声音就听不到了。这在某些场景下可以接受,但做产品时要考虑清楚。

14.4 知识体系:USB音频路由核心逻辑

下面这张图,是我根据多年经验总结的USB音频路由核心流程。你看完应该能对整个链路有个清晰的印象。

USB音频路由核心流程 USB音频设备插入 内核USB子系统枚举 UsbManager 发现设备 AudioService 注册设备 AudioPolicyManager 路由决策 关键点: 1. UAC 1.0/2.0 协议识别 2. 设备描述符解析 3. 路由策略定制 4. 延迟优化空间 延迟优化:缓冲区 + 异步模式

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 audiodmesg日志。这两个东西能告诉你90%的问题原因。剩下的10%,嗯,那就得靠经验和运气了。

USB音频路由,说白了就是一套「发现-注册-路由-优化」的流程。只要把每个环节的机制搞清楚了,遇到问题就不会慌。你想想看,是不是这个道理?