第十九章 音频系统(AudioFlinger):从启动到HAL的完整链路
音频系统,说白了就是Android设备发声和录音的“总指挥部”。我刚开始接触这部分时,觉得它就是个播放音乐的模块。后来才发现,音频系统涉及进程通信、策略决策、硬件抽象,复杂度远超想象。
这一章,我们聚焦AudioFlinger——它是音频系统的核心服务。我会从启动流程讲起,带你走通音频流管理、策略控制,最后落到HAL层交互。嗯,这些都是我在实际项目中踩过坑的地方。
19.1 AudioFlinger的启动:一场精心编排的初始化
AudioFlinger是Android的native服务,由mediaserver进程启动。它的启动顺序,决定了整个音频系统能否正常工作。
启动流程大致如下:
- mediaserver入口:main_mediaserver.cpp中调用AudioFlinger::instantiate()
- 服务注册:AudioFlinger继承自BnAudioFlinger,注册到ServiceManager
- 硬件抽象加载:通过HAL接口加载音频设备(primary、a2dp、usb等)
- 线程池创建:初始化MixerThread、RecordThread等工作线程
- 策略服务绑定:与AudioPolicyService建立双向通信
核心要点:AudioFlinger启动时,会加载HAL库中的audio_hw_device_t结构体。这个结构体包含了所有硬件操作函数指针。如果HAL加载失败,整个音频系统都会瘫痪。
我记得有一次,客户反馈设备没有声音。排查了半天,发现是HAL库的so文件权限不对,导致AudioFlinger加载时直接返回NULL。嗯,这种问题在系统集成时特别常见。
19.2 音频流管理:AudioTrack与AudioRecord
音频流管理,说白了就是“谁在播放,谁在录音”。Android提供了两个核心类:
- AudioTrack:用于音频输出(播放)
- AudioRecord:用于音频输入(录音)
它们都是客户端API,底层通过Binder与AudioFlinger通信。
19.2.1 AudioTrack的工作流程
创建一个AudioTrack,大致经历以下步骤:
- 应用层调用AudioTrack构造函数,传入采样率、通道数、格式等参数
- AudioTrack通过Binder调用AudioFlinger的createTrack()
- AudioFlinger根据音频策略,选择合适的输出设备和线程
- 在MixerThread中分配一个Track对象,返回给客户端
- 客户端通过共享内存(AudioBufferProvider)写入音频数据
// 典型的AudioTrack创建代码
AudioTrack* track = new AudioTrack(
AUDIO_STREAM_MUSIC, // 流类型
44100, // 采样率
AUDIO_FORMAT_PCM_16_BIT, // 格式
AUDIO_CHANNEL_OUT_STEREO, // 通道
bufferSize, // 缓冲区大小
AUDIO_OUTPUT_FLAG_NONE // 标志
);
track->start();
个人经验:缓冲区大小的选择很关键。太小会导致音频卡顿(underrun),太大会增加延迟。我一般建议游戏应用使用较小的缓冲区(~10ms),音乐播放使用较大的缓冲区(~50ms)。
19.2.2 AudioRecord的录音链路
录音流程与播放类似,但方向相反:
- 客户端创建AudioRecord,指定音频源(如MIC)
- AudioFlinger创建RecordThread,从HAL读取数据
- 数据通过共享内存传递到客户端
这里有个坑:录音权限检查是在AudioFlinger侧做的。我曾经遇到一个bug,应用明明申请了权限,但录音还是失败。后来发现是AudioPolicyService的路由策略把录音源路由到了错误的设备上。
19.3 音频策略:AudioPolicyService的决策艺术
AudioPolicyService(简称APS)是音频系统的“大脑”。它决定:
- 哪个应用的声音该从哪个设备输出
- 多个音频流如何混合
- 插入耳机时是否切换输出设备
APS与AudioFlinger的关系,就像指挥官与执行者。APS制定策略,AudioFlinger负责执行。
19.3.1 策略规则配置
策略规则定义在audio_policy_configuration.xml中。这个文件描述了:
| 配置项 | 说明 |
|---|---|
| 设备模块 | primary、a2dp、usb等HAL模块 |
| 输出描述符 | 每个输出设备的属性(采样率、通道数等) |
| 路由规则 | 流类型到输出设备的映射 |
举个例子,当插入耳机时,APS会检查路由规则,将AUDIO_STREAM_MUSIC从扬声器切换到耳机。这个过程是实时的,不需要应用层干预。
避坑指南:我曾经在定制车载系统时,修改了audio_policy_configuration.xml,结果导致蓝牙电话没有声音。原因是忘记配置AUDIO_STREAM_VOICE_CALL到蓝牙 SCO 的路由规则。所以,修改策略文件时一定要全面测试所有音频场景。
19.4 HAL层交互:AudioFlinger与硬件的桥梁
HAL(Hardware Abstraction Layer)是AudioFlinger与硬件驱动之间的接口。Android定义了标准的HAL接口,芯片厂商(如高通、MTK)负责实现。
19.4.1 HAL接口结构
核心结构体是audio_hw_device_t:
struct audio_hw_device_t {
struct hw_device_t common;
// 打开/关闭输出流
int (*open_output_stream)(...);
int (*close_output_stream)(...);
// 打开/关闭输入流
int (*open_input_stream)(...);
int (*close_input_stream)(...);
// 设置参数
int (*set_parameters)(...);
int (*get_parameters)(...);
};
AudioFlinger通过dlopen()加载HAL库,然后调用这些函数指针。每个函数对应一个硬件操作。
19.4.2 数据流路径
从应用层到硬件的完整数据流:
- 应用写入AudioTrack缓冲区(用户空间)
- AudioFlinger的MixerThread混合多个Track
- 混合后的数据写入共享内存
- HAL的write()函数将数据送入内核驱动
- 驱动通过DMA传输到音频编解码器(Codec)
- Codec输出模拟信号到扬声器/耳机
录音路径则相反:麦克风信号 → Codec → DMA → 内核 → HAL read() → AudioFlinger → 应用。
关键点:HAL层的write()和read()是阻塞调用。如果硬件响应慢,会导致整个音频管道阻塞。我遇到过一个问题:某款低端芯片的HAL write()实现中,加了一个不必要的usleep(),导致音频播放出现周期性卡顿。去掉后问题解决。
19.5 知识体系总览
为了让你更直观地理解整个音频系统的架构,我画了一张图:
这张图展示了音频数据的完整流转路径。从应用层到内核层,每一层都有明确的职责。AudioFlinger处于承上启下的位置,既要处理上层的音频流请求,又要通过HAL控制底层硬件。
19.6 实战中的常见问题
最后,分享几个我在项目中遇到的典型问题:
- 音频焦点冲突:两个应用同时播放音乐,导致声音混乱。解决方案是正确使用AudioManager的requestAudioFocus()。
- 设备切换延迟:插入耳机后,声音还在扬声器播放。原因是AudioPolicyService的路由切换逻辑有延迟,需要优化策略配置。
- HAL兼容性问题:升级Android版本后,旧的HAL库不兼容新接口。建议在HAL实现中严格遵循版本规范。
我的建议:调试音频问题时,善用dumpsys media.audio_flinger和dumpsys media.audio_policy命令。它们能输出当前音频系统的完整状态,包括所有Track、线程、设备信息。这是定位问题的第一手资料。
音频系统是个大话题,但核心脉络就是启动、管理、策略、HAL这四块。你只要把这条主线理清楚,遇到问题就能快速定位到具体环节。嗯,下一章我们会深入音频策略的细节,看看AudioPolicyService是如何做出那些“聪明”的决策的。