3、音频策略与路由:AudioPolicyService与AudioFlinger的关系、音频策略决策流程、音频路由切换机制

好,我们进入第三章。这一章,我打算聊聊音频策略和路由。

说实话,很多做Android音频开发的同行,一开始都容易把AudioFlinger和AudioPolicyService搞混。它们俩到底谁管谁?音频路由又是怎么切来切去的?

嗯,今天我们就把它彻底理清楚。

3.1 AudioPolicyService与AudioFlinger:谁说了算?

先打个比方。你把AudioFlinger想象成一个音响师,他手里有调音台、功放、喇叭。他负责把音频数据流送到正确的硬件设备上,保证声音能出来。

那AudioPolicyService呢?它更像一个导演。导演决定:现在这个场景,应该用哪个喇叭?是听筒还是扬声器?蓝牙耳机连上了,要不要切过去?

说白了,AudioPolicyService是策略制定者,AudioFlinger是策略执行者

我个人习惯把它们的交互画成一张图,这样更直观:

AudioPolicyService 与 AudioFlinger 关系图 应用层 (App / MediaPlayer / AudioTrack) AudioPolicyService 策略决策:路由、音量、设备选择 AudioFlinger 音频数据流管理、混音、设备驱动 策略指令 设备状态回调 Audio HAL (硬件抽象层) 打开/关闭设备 请求策略 创建Track

你看,应用层创建AudioTrack时,会同时跟APS和AF打交道。APS决定“用哪个设备”,AF负责“怎么把数据送过去”。

核心区别一句话:AudioPolicyService管“路由策略”,AudioFlinger管“音频数据流”。两者通过Binder通信,APS下发指令,AF执行并回调状态。

3.2 音频策略决策流程:导演是怎么想的?

音频策略决策,说白了就是APS在回答一个问题:“现在这个音频流,应该从哪个设备出去?”

决策流程大致分三步:

  1. 收集信息:APS会监听各种事件,比如耳机插入、蓝牙连接、电话状态变化、应用启动播放等。
  2. 匹配规则:APS内部维护了一套策略规则,定义在audio_policy_configuration.xmlaudio_policy_engine_*.xml中。它会根据当前设备状态、音频流类型(媒体、通话、闹钟等)、以及应用请求,找到最合适的输出设备。
  3. 下发指令:决策完成后,APS调用AudioFlinger的接口,比如setDeviceConnectionState()setForceUse(),让AF去切换硬件设备。

举个例子。你插上耳机听歌,APS会收到“有线耳机已插入”的通知。它查一下规则:媒体流优先走耳机。于是它告诉AF:“把媒体流的输出路由切换到耳机。” AF收到指令,关闭扬声器通路,打开耳机通路。整个过程不到100毫秒。

避坑指南:我曾经在一个项目里遇到耳机插拔时声音卡顿的问题。查了半天,发现是APS决策太快,但AF底层设备切换还没完成。后来我们在APS和AF之间加了一个状态同步的等待机制,问题才解决。所以,策略决策不仅要“快”,还要“稳”。

3.3 音频路由切换机制:声音是怎么拐弯的?

音频路由切换,就是声音从一条路径换到另一条路径。你想想看,手机里可能有十几个音频设备:扬声器、听筒、有线耳机、蓝牙A2DP、蓝牙SCO、USB耳机、HDMI...它们之间怎么切?

Android的路由切换机制,核心是“动态路由”。它不是静态写死的,而是根据实时状态动态调整。

具体来说,切换过程分这几个步骤:

  • 设备枚举:AF启动时,会通过HAL枚举所有可用的音频设备。每个设备有一个唯一的audio_devices_t标识,比如DEVICE_OUT_WIRED_HEADSETDEVICE_OUT_SPEAKER
  • 策略匹配:APS根据当前场景,从策略引擎中查找匹配的AudioPolicyMixAudioOutputDescriptor。比如,电话进来时,策略引擎会强制把通话路由切换到DEVICE_OUT_EARPIECEDEVICE_OUT_BLUETOOTH_SCO
  • 设备切换:APS调用AF的setOutputDevice()方法。AF会先停止当前设备的音频流,然后打开新设备,最后恢复播放。这个过程涉及到底层HAL的open_output_stream()close_output_stream()调用。

这里有一个关键点:音频焦点。APS在切换路由时,还会考虑音频焦点。比如,你正在听音乐,突然来了导航语音。APS会判断导航语音是“短暂提示”,于是它不会切换路由,而是让导航语音混音到当前输出设备上。但如果来了电话,APS会抢夺焦点,强制切换路由。

注意:路由切换不是瞬间完成的。从APS下发指令到AF完成切换,中间有几十毫秒的延迟。如果应用层在切换期间继续发送音频数据,可能会导致数据丢失或杂音。我建议在应用层监听AudioManager.ACTION_AUDIO_BECOMING_NOISY广播,在路由切换前暂停播放,切换完成后再恢复。

3.4 实战:一个典型的路由切换场景

我们来看一个完整的场景:蓝牙耳机连接后,音乐自动从扬声器切换到蓝牙耳机。

  1. 蓝牙连接:蓝牙服务通知APS,蓝牙A2DP设备已连接。
  2. APS决策:APS检查策略规则,发现媒体流支持蓝牙A2DP设备,且当前没有其他更高优先级的设备(比如有线耳机)。于是它决定:将媒体流路由切换到蓝牙。
  3. AF执行:APS调用AudioFlinger::setOutputDevice(),传入蓝牙设备的ID。AF找到当前媒体流的输出线程,关闭扬声器设备,打开蓝牙A2DP设备。
  4. 数据重定向:AF将后续的音频数据编码成SBC或AAC格式,通过蓝牙协议栈发送到耳机。
  5. 状态回调:AF完成切换后,回调APS,告知切换成功。APS再通知应用层,音频路由已变更。

整个过程,应用层几乎无感知。但如果你在代码里做了不合理的假设,比如硬编码了输出设备,那就会出问题。

总结一下:音频策略与路由,是Android音频系统的“大脑”和“神经”。APS负责思考“走哪条路”,AF负责执行“怎么走”。两者配合得好,用户就感觉不到切换的存在。配合得不好,就会出现声音断断续续、延迟、甚至无声的问题。

嗯,这一章就到这里。下一章我们会深入AudioFlinger的混音器,看看它是怎么把多个音频流混合成一个的。到时候再聊。


公众号:蓝海资料掘金营,微信deep3321