第1章:音频子系统概述
大家好,我是老张。在三星Exynos平台摸爬滚打了十年,今天咱们聊聊音频子系统。说实话,音频这玩意儿看着简单,不就是放个声儿吗?但真做起来,坑多得很。我刚开始接触Exynos 4412时,就被音频驱动折腾得够呛。
三星Exynos音频架构演进
三星Exynos的音频架构,其实经历了三个阶段。我个人习惯把它们叫做:
- 早期阶段(Exynos 4系列):那时候音频功能单一,主要靠I2S接口外挂Codec。我记得Exynos 4412上,音频路径特别简单,就是CPU→I2S→WM8994。嗯,当时觉得挺清爽的。
- 中期阶段(Exynos 5/7系列):开始集成音频DSP了。Exynos 5422上首次出现了内部音频DSP,专门处理低功耗音频场景。说白了,就是让DSP干活,CPU可以歇着。
- 现代阶段(Exynos 9/2200系列):音频子系统已经是个独立模块了。有独立的音频总线、多个DMA通道、甚至支持AI语音处理。你想想看,这复杂度比早期翻了不止十倍。
核心变化:从"外挂Codec"到"片上音频系统",这是三星Exynos音频架构最大的演进方向。
音频子系统在SoC中的位置
音频子系统在SoC里到底在哪儿?我画个简单的框图给你看:
+------------------+ +------------------+
| CPU Core | | Memory |
| (应用处理器) | | (DDR) |
+--------+---------+ +--------+---------+
| |
| 系统总线(AMBA/AXI) |
+------------------------+
|
+------------+------------+
| | |
+--------+------+ +---+--------+ +--+-----------+
| 音频DSP | | DMA控制器 | | I2S/PDM接口 |
| (低功耗处理) | | (音频专用) | | (数字音频IO) |
+---------------+ +------------+ +--------------+
| | |
+------------+------------+
|
+-------+--------+
| 音频Codec |
| (ADC/DAC) |
+----------------+
为什么会这样布局?我解释一下:音频数据流从内存出发,经过DMA直接送到I2S接口,或者先经过DSP处理再送出去。CPU只在需要控制时介入,数据通路尽量不经过CPU。这样做的好处是——省电。我在Exynos 5422项目上测过,纯DSP播放音乐,功耗比CPU播放低了将近70%。
音频驱动栈全景图
音频驱动栈,说白了就是软件怎么一层层把音频数据送到硬件里。我习惯从下往上讲:
| 层级 | 组件 | 职责 |
|---|---|---|
| 应用层 | AudioFlinger / TinyAlsa | 管理音频策略、混音 |
| 框架层 | ALSA lib / Audio HAL | 提供标准API、抽象硬件差异 |
| 内核层 | ASoC / ALSA Core | 管理Codec、DAI、DMA |
| 硬件层 | I2C/SPI/I2S/PDM | 物理传输音频数据 |
这里我要重点说说ASoC。ASoC是ALSA在嵌入式领域的扩展,专门解决SoC音频的复杂性问题。我刚开始接触时,觉得ASoC的架构很绕——又是DAI、又是Codec、又是Platform。但后来我明白了,它其实就三部分:
- Codec驱动:管音频编解码芯片的寄存器配置。比如音量控制、采样率设置。
- DAI驱动:管数字音频接口的时序。比如I2S的帧同步、位时钟。
- Platform驱动:管DMA传输。把内存里的音频数据搬到I2S FIFO里。
我的经验:调试音频驱动时,先确认Platform驱动能正常传输数据,再调Codec。我曾经在Exynos 7420上,花了两天查一个无声问题,最后发现是DMA的burst size配置错了。嗯,从那以后我调试音频驱动,第一件事就是检查DMA配置。
硬件抽象层(HAL)
HAL层是Android系统特有的。它把内核驱动的细节封装起来,给上层提供统一的接口。说白了,就是让上层不用管底层用的是Exynos还是高通。
在三星Exynos平台上,HAL层主要做这几件事:
- 设备管理:打开/关闭音频设备,比如耳机、扬声器、麦克风。
- 路由管理:决定音频走哪条路径。比如插上耳机时,自动从扬声器切换到耳机。
- 参数配置:设置采样率、位深、通道数等。
- 低延迟处理:通过直接与DSP交互,实现低延迟音频。
避坑指南:我曾经在Exynos 9810上遇到一个HAL层的问题——插拔耳机时,音频路由切换有爆音。查了两天才发现,是HAL层在切换路由时,没有先静音Codec。解决方案很简单:切换前先调用Codec的静音函数,切换完再取消静音。就这一行代码,爆音问题就解决了。
小结
音频子系统,说白了就是一条数据通路。从应用层到硬件层,每一层都有自己的职责。作为驱动工程师,我们最常打交道的是内核层的ASoC和硬件层的I2S/DMA。记住一点:音频驱动调试,先看数据能不能走通,再看音质好不好。
下一章,我会详细讲ALSA框架的核心数据结构——snd_card、snd_pcm、snd_control。这些是写音频驱动的基础,也是面试常考的内容。咱们下章见。