16、Audio系统架构:AudioFlinger、AudioPolicyService、HAL层与音频路由
音频系统,说白了就是Android设备发声的“总指挥部”。
我刚开始啃这部分源码时,觉得它就是个黑盒——上层播放个MP3,底层喇叭就响了。但等你真正深入AudioFlinger和AudioPolicyService之后,会发现里面的设计相当精巧。今天我们就把它拆开看看。
16.1 整体架构:谁在管什么?
Android音频系统分三层:
- 应用层:MediaPlayer、AudioTrack 等API调用者。
- 框架层(Native):AudioFlinger(混音与输出)、AudioPolicyService(策略与路由)。
- HAL层:直接操作硬件,比如扬声器、耳机、蓝牙。
我个人习惯把AudioFlinger比作“音频搬运工”,把AudioPolicyService比作“交通指挥官”。
核心逻辑:应用只管扔数据,AudioFlinger负责混音和发送,AudioPolicyService决定数据走哪条路(扬声器?耳机?还是蓝牙?)。
16.2 AudioFlinger:混音与输出
AudioFlinger是Android音频系统的“心脏”。它负责接收所有应用的音频数据,把它们混在一起,然后送给HAL层播放。
我记得第一次看它的源码时,最让我惊讶的是它用了多个线程来管理不同的输出设备。每个设备(比如内置扬声器、耳机、蓝牙A2DP)都有自己的混音线程(MixerThread)。
避坑指南:我曾经在调试一个蓝牙音箱延迟问题时,发现AudioFlinger的混音线程优先级被降了。结果就是音频数据送不到蓝牙模块。嗯,线程优先级和CPU亲和性,这两个参数一定要盯紧。
核心类关系:
AudioFlinger:主服务,管理所有音频设备和混音线程。MixerThread:负责混音,把多个Track的数据叠加。PlaybackThread:负责将混音后的数据写入HAL。RecordThread:负责从HAL读取录音数据。
你想想看,如果两个App同时播放音乐,AudioFlinger会把它们的PCM数据按比例叠加,再统一输出。这个混音算法其实不复杂,但性能要求很高——毕竟不能有卡顿。
16.3 AudioPolicyService:策略与路由
AudioPolicyService(简称APS)是“大脑”。它不直接处理音频数据,而是决定音频数据应该走哪条路。
举个例子:你插上耳机时,APS会检测到耳机插入事件,然后通知AudioFlinger把输出路由切换到耳机。拔掉耳机时,又自动切回扬声器。
为什么会这样?因为APS维护了一个音频策略规则表。这个表定义了不同音频流(音乐、通话、导航、铃声)的默认输出设备。
| 音频流类型 | 默认输出设备 | 可切换设备 |
|---|---|---|
| MUSIC | 扬声器 | 耳机、蓝牙A2DP |
| VOICE_CALL | 听筒 | 耳机、蓝牙SCO |
| ALARM | 扬声器 | (强制扬声器) |
| NOTIFICATION | 扬声器 | 耳机 |
注意:APS的策略文件通常位于 /vendor/etc/audio_policy_configuration.xml。我曾经遇到一个设备,因为XML里漏配了蓝牙A2DP的输出,导致蓝牙音乐无声。查了两天才找到原因——就是少了一行配置。
16.4 HAL层:硬件抽象
HAL层是AudioFlinger和硬件之间的“翻译官”。它定义了一组标准接口,比如open_output_stream、write、close_output_stream。
不同的硬件厂商实现自己的HAL库。比如高通有audio.primary.msm8998.so,MTK有audio.primary.mt6765.so。
我个人习惯在调试音频问题时,先检查HAL层是否正常加载。用lshal或者dumpsys media.audio_flinger都能看到当前使用的HAL模块。
关键点:HAL层的write函数是同步的。AudioFlinger调用它后,必须等它返回才能继续送下一帧数据。所以如果HAL实现得不好(比如驱动有延迟),整个音频系统都会卡顿。
16.5 音频路由:数据怎么走?
音频路由,说白了就是数据从App到喇叭的路径。我画个简化的流程:
- App调用
AudioTrack.write(),数据进入AudioFlinger的Track Buffer。 - AudioFlinger的MixerThread把多个Track的数据混音。
- 混音后的数据写入
PlaybackThread的环形缓冲区。 PlaybackThread调用HAL的write函数,把数据送到硬件。- 硬件驱动把数字信号转成模拟信号,推动扬声器振膜。
这里有个细节:路由切换时,AudioFlinger会先停止当前输出线程,再启动新线程。如果切换时机不对,就会出现“噗”的一声爆音。
我的经验:解决爆音问题,可以在HAL层加一个静音过渡(mute ramp)。也就是切换路由时,先把音量渐变到0,切换完成后再渐变回来。这个方案我在两个项目里用过,效果很好。
16.6 实战:如何定位音频无声问题?
假设你遇到一个Bug:插上耳机后,音乐还是从扬声器出来。怎么排查?
- 第一步:检查APS是否检测到耳机插入。用
dumpsys audio查看mConnectedDevices。 - 第二步:检查策略规则。看
audio_policy_configuration.xml中MUSIC流是否允许路由到耳机。 - 第三步:检查AudioFlinger的输出线程。用
dumpsys media.audio_flinger查看当前活动的PlaybackThread。 - 第四步:检查HAL层。用
tinymix查看音频通路是否真的切到了耳机。
我曾经遇到一个案例:APS已经正确切换了路由,但HAL层的混音器(MIXER)没有更新通路。最后发现是HAL实现里漏掉了set_parameters的回调处理。嗯,这种问题往往藏得比较深。
16.7 小结
AudioFlinger负责干活,AudioPolicyService负责指挥,HAL层负责跟硬件打交道。三者配合,才能让Android设备正常发声。
音频路由看似简单,但涉及线程切换、策略匹配、硬件控制,任何一个环节出问题都会导致无声或异常。我建议你在调试时,先从dumpsys入手,把当前状态摸清楚,再逐层往下查。
好了,这一章就到这里。记住:音频系统没有玄学,只有还没被发现的配置错误或代码Bug。