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里?这个过程其实很经典。

  1. 应用程序调用 write() 到ALSA设备节点(如 /dev/snd/pcmC0D0p)。
  2. ALSA库(libasound)把数据通过系统调用 ioctl() 或 mmap() 传给内核。
  3. 内核ALSA核心把数据放入DMA缓冲区。
  4. DMA控制器自动把缓冲区数据搬运到I2S控制器的FIFO。
  5. I2S控制器按位时钟把数据串行发送给Codec。
  6. Codec把数字信号转成模拟信号,驱动喇叭发声。

整个过程,内核只负责配置和启动,真正的数据搬运是DMA硬件完成的。这就是为什么音频播放不占用CPU的原因。

避坑指南:我曾经在调试一个项目时,发现播放声音有杂音。查了半天,原来是DMA缓冲区大小配置不合理。缓冲区太小,CPU来不及填充数据,导致「欠载」(underrun)。缓冲区太大,又会导致播放延迟过高。这个平衡点,需要根据你的系统负载和音频采样率来调整。

用户空间和内核空间交互的核心数据结构是 snd_pcm_substream。它代表一个PCM流(播放或录音)。内核通过它管理DMA缓冲区、硬件参数(采样率、格式、声道数)和指针位置。

嗯,这里有个关键点:ALSA支持两种操作模式:阻塞和非阻塞。阻塞模式下,应用程序调用 write() 时,如果DMA缓冲区满了,它会休眠等待。非阻塞模式则直接返回错误。我个人习惯在调试阶段用阻塞模式,简单可靠。产品上线时,再根据需求切换到非阻塞或异步模式。

好了,这一章的内容就到这里。音频硬件接口、ALSA架构、数据流路径,这三块是后续所有章节的基础。你把这些搞清楚了,后面写驱动时就不会迷路。