1、Linux音频子系统概览:音频硬件基础(I2S、PCM、AC97)、ALSA架构简介、用户空间与内核空间交互
好,咱们正式开始。音频驱动这块,说实话,我当年刚接触时也是一头雾水。硬件术语一堆,软件架构又绕。但别怕,咱们一步步来。这一章,我先带你看看音频硬件长什么样,再聊聊ALSA这个大家伙是怎么搭起来的。
1.1 音频硬件基础:那些绕不开的接口
做音频驱动,你首先得跟硬件打交道。常见的音频数字接口就那么几种:I2S、PCM、AC97。我习惯把它们看作是「音频数据的搬运工」。
1.1.1 I2S(Inter-IC Sound)
这是目前最主流的数字音频接口。说白了,它就是一条专门传声音数据的总线。我在项目中遇到过,很多SoC的音频外设都挂在这个总线上。
I2S总线通常有三条线:
- SCK(位时钟):决定每个bit的传输速度。比如48kHz采样率、16bit、双声道,那SCK就是48k * 16 * 2 = 1.536MHz。
- WS(字选择/帧时钟):区分左右声道。高电平是左声道,低电平是右声道,或者反过来。
- SD(串行数据):真正的音频数据流。
嗯,这里要注意:I2S标准里,数据是在WS变化后的第二个时钟沿开始传输的。但有些厂商会搞出「左对齐」或「右对齐」的变种。你写驱动时,一定要先看Codec的datasheet,确认它用的是哪种格式。
核心要点:I2S是同步串行接口,主设备提供时钟,从设备接收时钟。驱动里配置好主从模式,数据就能跑起来了。
1.1.2 PCM(Pulse Code Modulation)接口
别被名字骗了。这里的PCM接口,跟音频编码里的PCM是两码事。它其实是一种时分复用的数字音频接口,跟I2S很像,但更灵活。
PCM接口可以支持多路音频数据在同一条线上传输。比如4个声道的数据,可以分时占用同一条SD线。这在一些蓝牙音频芯片或数字麦克风阵列里很常见。
我曾经调试过一个项目,Codec支持PCM接口,但SoC只支持I2S。折腾了半天,最后发现可以通过配置帧同步的宽度和位置来模拟I2S时序。说白了,PCM就是I2S的「超集」,兼容起来并不难。
1.1.3 AC97(Audio Codec '97)
这个接口现在基本被淘汰了,但老设备上还能见到。AC97是一个固定的框架,它把音频编解码、混音、模拟输入输出都集成在一个芯片里。接口是固定的5线制:位时钟、帧同步、数据输出、数据输入、复位。
AC97最大的问题是延迟高、带宽有限。你想想看,它设计于90年代,那时候对高保真音频还没那么苛刻。现在做产品,除非是维护老项目,否则我建议直接上I2S。
| 接口 | 特点 | 适用场景 |
|---|---|---|
| I2S | 简单、主流、延迟低 | 绝大多数音频Codec、SoC |
| PCM | 灵活、支持多路时分复用 | 蓝牙音频、数字麦克风阵列 |
| AC97 | 固定框架、延迟高 | 老旧PC主板、嵌入式老平台 |
1.2 ALSA架构简介:从用户到内核的桥梁
硬件接口搞清楚了,接下来就是软件层面。Linux音频子系统叫ALSA(Advanced Linux Sound Architecture)。我刚开始接触时,觉得它就是个「黑盒子」——应用程序调用API,声音就出来了。但驱动开发者必须把这个盒子拆开看。
ALSA的架构分三层:
- 用户空间:应用程序(如aplay、arecord)通过ALSA库(libasound)与内核交互。
- 内核空间:ALSA核心框架(core) + 硬件驱动(driver)。
- 硬件层:实际的音频Codec、DMA控制器、I2S控制器。
说白了,ALSA就是一套标准化的接口。它定义好了「你该怎么做」,你只需要按照这个框架去实现具体的硬件操作。
个人经验:我建议你从 sound/soc/ 目录下的代码开始看。这个目录是ALSA System on Chip(ASoC)子系统,专门为嵌入式音频设计的。它把驱动分成了三部分:Codec驱动、平台驱动(DMA)、板级驱动(连线)。这样分层,代码复用性极高。
1.3 用户空间与内核空间交互:数据怎么跑起来的?
你想想看,用户空间的播放器(比如mpg123)怎么把音乐数据送到Codec里?这个过程其实很经典。
- 应用程序调用
write()到ALSA设备节点(如/dev/snd/pcmC0D0p)。 - ALSA库(libasound)把数据通过系统调用
ioctl()或mmap()传给内核。 - 内核ALSA核心把数据放入DMA缓冲区。
- DMA控制器自动把缓冲区数据搬运到I2S控制器的FIFO。
- I2S控制器按位时钟把数据串行发送给Codec。
- Codec把数字信号转成模拟信号,驱动喇叭发声。
整个过程,内核只负责配置和启动,真正的数据搬运是DMA硬件完成的。这就是为什么音频播放不占用CPU的原因。
避坑指南:我曾经在调试一个项目时,发现播放声音有杂音。查了半天,原来是DMA缓冲区大小配置不合理。缓冲区太小,CPU来不及填充数据,导致「欠载」(underrun)。缓冲区太大,又会导致播放延迟过高。这个平衡点,需要根据你的系统负载和音频采样率来调整。
用户空间和内核空间交互的核心数据结构是 snd_pcm_substream。它代表一个PCM流(播放或录音)。内核通过它管理DMA缓冲区、硬件参数(采样率、格式、声道数)和指针位置。
嗯,这里有个关键点:ALSA支持两种操作模式:阻塞和非阻塞。阻塞模式下,应用程序调用 write() 时,如果DMA缓冲区满了,它会休眠等待。非阻塞模式则直接返回错误。我个人习惯在调试阶段用阻塞模式,简单可靠。产品上线时,再根据需求切换到非阻塞或异步模式。
好了,这一章的内容就到这里。音频硬件接口、ALSA架构、数据流路径,这三块是后续所有章节的基础。你把这些搞清楚了,后面写驱动时就不会迷路。