1. 音频系统全景:从麦克风到扬声器的信号流

各位同学,咱们今天先不急着写代码。做音频驱动开发,第一件事是把整个系统看透。你想想看,从你对着麦克风说一句话,到扬声器放出声音,中间经历了什么?我见过不少工程师,上来就调 I2C 寄存器,结果声音出不来,连是硬件没通还是软件没配都不知道。

说白了,音频系统就是一条流水线。声音从模拟世界进来,经过数字化处理,再变回模拟信号推出去。咱们嵌入式 Linux 下的音频系统,核心就三块:硬件组成、数据通路、软件栈。今天我把这三块串起来讲,你心里就有张全景图了。

1.1 硬件组成:Codec、DMA、I2S、DAI

先看硬件。一个典型的嵌入式音频系统,硬件上离不开这几个角色:

组件 全称 一句话说明
Codec Coder-Decoder 模拟↔数字转换,音频编解码
DMA Direct Memory Access 搬运音频数据,不占 CPU
I2S Inter-IC Sound 数字音频总线,传 PCM 数据
DAI Digital Audio Interface CPU 侧的数字音频接口

我习惯把 Codec 比作「翻译官」。麦克风进来的模拟信号,Codec 用 ADC 转成数字;要输出到扬声器,再用 DAC 转回模拟。Codec 里还藏着混音器、增益控制、滤波器这些功能。嗯,这里要注意:Codec 的寄存器配置是驱动调试的重灾区,我踩过不少坑。

DMA 呢?它就是「搬运工」。音频数据量大,CPU 要是亲自搬,别的活就别干了。DMA 控制器直接在内存和外设之间搬数据,搬完给你发个中断。我在项目里遇到过 DMA 描述符没配好,导致音频断断续续,查了两天才发现是链表指针写错了。

I2S 是数字音频的「高速公路」。它用三条线:位时钟(BCLK)、帧时钟(LRCK)、数据线(SD)。左右声道的数据在帧时钟的上升沿和下降沿分别传输。标准 I2S 协议下,数据在 BCLK 的下降沿变化,接收端在上升沿采样。这个时序要是对不上,出来的声音就是噪音。

DAI 是 CPU 内部集成的数字音频接口,负责把 DMA 送来的数据按 I2S 协议发出去,或者把 I2S 总线上的数据收进来。不同的 SoC 厂商叫法不一样,比如 STM32 叫 I2S 外设,i.MX 叫 SAI,但本质都是 DAI。

核心关系:DMA ↔ DAI ↔ I2S 总线 ↔ Codec ↔ 模拟输入/输出

数据流:麦克风 → Codec(ADC) → I2S → DAI → DMA → 内存

1.2 音频信号流:从麦克风到扬声器

咱们走一遍完整的信号流。你对着麦克风说话,声波变成微弱的电信号。Codec 里的前置放大器先把这个信号放大,然后 ADC 以 48kHz 的采样率、16bit 的精度,把模拟信号变成一串数字——这就是 PCM 数据。

这些 PCM 数据通过 I2S 总线,一帧一帧地送到 CPU 的 DAI 接口。DAI 收到数据后,DMA 控制器自动把它们搬到内存里的一个环形缓冲区。应用程序从缓冲区读走数据,做处理(比如降噪、回声消除),再写回另一个缓冲区。播放时流程反过来:DMA 从内存搬数据到 DAI,DAI 通过 I2S 发给 Codec,Codec 的 DAC 转成模拟信号,经过功放推动扬声器。

整个过程是流水线式的。我当年调试一个语音通话项目,发现录音有咔咔声,最后定位到是 DMA 的 burst 大小和 I2S 的帧大小不匹配,导致数据搬运节奏乱了。这种问题,没有全景图根本想不到。

避坑指南:我曾经在调试 I2S 时钟时,忽略了 Codec 的主从模式配置。Codec 作为从设备时,BCLK 和 LRCK 由 CPU 提供;作为主设备时,Codec 自己产生时钟。两边模式没对上,I2S 总线直接罢工。记住:主从模式必须一致。

1.3 Linux 音频软件栈:ASoC、ALSA、用户空间

硬件讲完了,软件栈怎么组织?Linux 音频软件栈分三层,我画了张图帮你理解:

Linux 音频软件栈全景 用户空间 应用程序(aplay、arecord、PulseAudio、PipeWire) ALSA Lib(libasound) 内核空间 ALSA Core(PCM / Control / MIDI) ASoC 框架 Machine Platform Codec Codec 硬件层 I2C(Codec 控制)/ I2S(音频数据)/ DMA / GPIO Codec 芯片(WM8960、TLV320AIC3x、ES8316 等) 用户空间通过 ALSA Lib 调用内核 ALSA Core,ASoC 框架封装了 Machine/Platform/Codec 驱动

从下往上看:

  • 硬件层:Codec 芯片、I2S 总线、DMA 控制器、GPIO 控制脚。这是物理基础。
  • 内核空间:ALSA Core 提供统一的 PCM 和 Control 接口。ASoC 框架把驱动拆成 Machine、Platform、Codec 三部分,方便复用。
  • 用户空间:应用程序通过 ALSA Lib 访问音频设备。aplay、arecord 是调试利器,PulseAudio 和 PipeWire 做策略管理。

ALSA(Advanced Linux Sound Architecture)是 Linux 内核的音频子系统核心。它定义了 /dev/snd/pcmC0D0p 这样的设备节点,应用程序通过 open()、read()、write()、ioctl() 操作音频数据。ALSA Lib 封装了这些系统调用,提供更友好的 API。

ASoC(ALSA System on Chip)是专门为嵌入式系统设计的框架。为什么需要它?因为嵌入式 SoC 的音频接口五花八门,Codec 芯片也千奇百怪。ASoC 把驱动拆成三块:

  • Machine 驱动:描述板级硬件连接,比如哪个 DAI 接了哪个 Codec,用了哪些 GPIO。
  • Platform 驱动:处理 DAI 和 DMA 的配置,跟 SoC 强相关。
  • Codec 驱动:管理 Codec 芯片的寄存器,控制音量、采样率、电源管理等。

这种拆分的好处是,换一块 Codec 芯片,只需要写新的 Codec 驱动,Platform 和 Machine 基本不用动。我在项目中换过三次 Codec,每次都是写一个新的 Codec 驱动文件,然后改几行设备树,省了不少事。

注意:ASoC 的 DAPM(Dynamic Audio Power Management)是省电的关键,但也是调试的难点。我曾经因为 DAPM 路径没配好,导致 Codec 的某个模块一直没上电,录音静音。查了两天,最后用 amixer 看控件状态才发现问题。

1.4 用户空间工具:调试的起点

调试音频驱动,用户空间的工具是第一道防线。我常用的几个:

工具 用途 常用命令示例
aplay 播放 WAV 文件 aplay -D hw:0,0 test.wav
arecord 录制音频 arecord -D hw:0,0 -f S16_LE -r 48000 -d 5 test.wav
amixer 查看/设置混音器控件 amixer sset 'Master' 80%
speaker-test 扬声器测试 speaker-test -t sine -f 1000
cat /proc/asound/cards 查看声卡列表 确认驱动是否加载成功

拿到一块新板子,我第一件事就是跑 cat /proc/asound/cards,看看声卡有没有注册成功。然后 aplay -l 看 PCM 设备列表。如果这些都没有,说明驱动根本没加载,别急着调音频参数,先查硬件和驱动注册。

如果设备有了但没声音,我会用 amixer 检查所有控件的状态。很多 Codec 默认是静音的,需要手动打开。我遇到过好几次,折腾半天硬件,最后发现是 Master 音量被 mute 了。

个人习惯:我调试时会在板子上放一个 1kHz 的正弦波 WAV 文件。用 speaker-test -t sine -f 1000 也可以,但 WAV 文件更可控。如果播放有杂音,先确认文件本身没问题——我犯过这种低级错误。

好了,这一章咱们把音频系统的全景走了一遍。从麦克风到扬声器的信号流,硬件上 Codec、DMA、I2S、DAI 各司其职,软件上 ALSA 和 ASoC 分层协作。你心里有了这张图,后面写驱动、调 bug 就知道往哪个方向查了。