4、音频设备管理:设备枚举、描述符、连接与策略
好,我们继续往下走。今天聊的是音频设备管理,说白了就是 AudioFlinger 怎么知道系统里插了什么设备、这些设备长什么样、什么时候拔了、以及最终该用哪个。
这部分内容,我当年刚接触时觉得挺琐碎的。不就是枚举一下设备吗?后来踩了几个坑才明白——设备管理是整个音频路由的基石。你设备都搞错了,后面策略再漂亮也是白搭。
4.1 设备枚举:系统里到底有哪些音频设备?
Android 的设备枚举,不是像 PC 那样去扫描 PCI 总线。它走的是一条更「抽象」的路。
设备信息从哪里来?从 HAL 层来。AudioFlinger 启动时,会去加载 HAL 库(比如 audio.primary.xxx.so),然后调用 HAL 的 get_devices() 函数。这个函数会返回当前所有可用的设备列表。
我习惯把设备分成两类:
- 内置设备:比如手机的扬声器、听筒、内置麦克风。这些设备是焊死在板子上的,枚举一次基本不变。
- 外接设备:比如蓝牙耳机、USB 声卡、3.5mm 耳机。这些是热插拔的,需要动态监听。
枚举的时机有两个:
- AudioFlinger 初始化时:一次性拉取所有设备。
- 设备热插拔事件触发时:比如你插上耳机,HAL 会回调通知 AudioFlinger 重新枚举。
核心要点:枚举不是轮询,是事件驱动的。HAL 层通过 audio_pool_events_callback 把设备变化推给 AudioFlinger。
4.2 设备描述符:DeviceDescriptor 到底装了什么?
枚举回来的设备,不能只是一个字符串名字吧?Android 用 DeviceDescriptor 这个结构体来完整描述一个音频设备。
我给大家拆开看看,里面都有什么:
| 字段 | 含义 | 举例 |
|---|---|---|
mDeviceId |
设备唯一 ID | 0x2 (耳机), 0x1 (扬声器) |
mType |
设备类型(AudioDeviceType) | DEVICE_OUT_WIRED_HEADSET |
mAddress |
设备地址(蓝牙 MAC 或 USB 路径) | "00:11:22:33:44:55" |
mName |
设备名称(可读字符串) | "Speaker" 或 "JBL Tune 500BT" |
mEncapsulationModes |
支持的封装模式 | 用于 HDMI/DP 音频 |
mEncapsulationMetadataTypes |
元数据类型 | 同上 |
嗯,这里要注意一个细节:mType 是 Android 框架层定义的逻辑类型,而 mAddress 才是真正区分同类型不同设备的钥匙。比如两个蓝牙耳机,它们的 mType 都是 DEVICE_OUT_BLUETOOTH_A2DP,但 mAddress 不同。
避坑指南:我曾经在调试多蓝牙设备切换时,发现 AudioFlinger 总是选错设备。查了半天,原来是 mAddress 在 HAL 层没有正确填充,导致两个设备被当成同一个。所以,如果你遇到设备选择异常,先检查 mAddress 对不对。
4.3 设备连接与断开处理
设备不是永远在线的。耳机拔了、蓝牙断了、USB 声卡松了——这些都需要 AudioFlinger 及时响应。
处理流程大致是这样的:
- HAL 检测到事件:比如耳机插入中断触发,HAL 驱动层感知到。
- HAL 回调 AudioFlinger:通过
audio_pool_devices_callback把事件类型(连接/断开)和设备信息传上来。 - AudioFlinger 更新设备列表:在
AudioFlinger::onDeviceConnection()或onDeviceDisconnection()中,增删DeviceDescriptor。 - 通知策略管理器:告诉 AudioPolicyManager:「设备变了,你重新算一下路由吧」。
这里有个关键点:断开处理比连接处理更复杂。为什么?因为断开时,可能有音频流正在使用这个设备。你不能直接拔掉,得先做「优雅降级」。
警告:如果你在 HAL 层直接断开设备而不通知 AudioFlinger,会导致 AudioFlinger 还在往一个已经不存在的设备写数据。结果就是——无声,甚至 crash。我见过一个案例,某款手机在 USB 声卡热拔时必重启,就是因为 HAL 层没发断开事件。
4.4 设备选择策略:到底用哪个设备出声?
设备枚举好了,描述符也建好了,连接断开也能处理了。接下来就是灵魂拷问:现在有扬声器、有蓝牙耳机、还有 USB 声卡,到底用哪个?
这个决策不是 AudioFlinger 做的,是 AudioPolicyManager 做的。但 AudioFlinger 提供了决策所需的数据基础。
策略的核心逻辑,我总结为三条:
- 优先级规则:外接设备 > 内置设备。比如插了耳机,就优先用耳机,而不是扬声器。
- 策略覆盖:App 可以通过
AudioAttributes指定使用某个设备类型。比如游戏 App 可以强制走扬声器,即使插着耳机。 - 动态切换:设备连接/断开时,策略管理器会重新评估。比如蓝牙耳机连上了,就从扬声器切到蓝牙。
我给大家画个图,把整个流程串起来:
你看,整个链路很清晰。HAL 负责「感知」,AudioFlinger 负责「管理」,AudioPolicyManager 负责「决策」。各司其职。
最后说一个我个人的经验:设备选择策略不是一成不变的。不同厂商、不同产品形态,策略可能完全不同。比如车载系统,可能优先用蓝牙电话通道;而智能音箱,可能永远用内置扬声器。所以 Android 把策略逻辑放在 AudioPolicyManager 里,允许厂商定制。
总结一下:设备管理这块,核心就三件事——把设备找出来(枚举)、把设备描述清楚(Descriptor)、在设备变化时做出正确选择(策略)。搞懂了这三件事,音频路由的底层逻辑你就拿下了。