从 aplay 到 Speaker:一条完整的 ASoC 播放链路到底发生了什么?(八)
2026/9/5 8:39:08 网站建设 项目流程

目录

一、先从最简单的场景开始

二、第一站:aplay

三、WAV 文件和 PCM 到底是什么关系?

四、第二站:ALSA PCM

五、这里千万不要把 ALSA 和 ASoC 混为一谈

六、第三站:ASoC

七、第四站:DAI Link

八、第五站:hw_params()

九、为什么 hw_params() 失败以后就播放不了了?

十、第六站:set_fmt()

十一、为什么 I2S 配错会出现“有数据但没声音”?

十二、第七站:Clock

十三、音频为什么这么喜欢“时钟”?

十四、第八站:DMA

十五、DMA 为什么需要 Buffer?

十六、这里出现一个很重要的概念:Period

十七、第九站:CPU DAI

十八、第十站:I2S / TDM

十九、I2S 到底传了什么?

二十、第十一站:Codec DAI

二十一、DAC 做什么?

二十二、第十二站:DAPM

二十三、DAPM 就像“自动开关电工”

二十四、Mixer 也可能让你“有数据但没声音”

二十五、第十三站:PGA 和 Volume

二十六、第十四站:功放 PA

二十七、终于到了 Speaker

二十八、那么 aplay 成功到底说明了什么?

二十九、没有声音时应该怎么查?

1. 先确认声卡存在

2. 确认 PCM 能否打开

3. 看 Sample Rate

4. 看 DAI Format

5. 看 Clock

6. 看 DMA

7. 看 DAPM

8. 看 Mixer

9. 看 Codec Register

三十、为什么我们要花这么大篇幅理解这条链?

三十一、把整个播放过程压缩成一张图

三十二、到这里,传统 ASoC 基础基本闭环

三十三、下一步:为什么 Qualcomm Audio 又突然复杂起来了?


前面七章,我们一直在认识 ASoC 的“零件”:

ALSA ASoC CPU DAI Codec DAI Machine Driver Device Tree DAPM

零件认识得差不多了,现在终于可以把它们装起来。

这一章我们不再纠结某个结构体有多少成员,而是回答一个非常实际的问题:

我执行aplay test.wav后,到底是谁在干什么?

最终,我们要把这条链路跑通:

aplay ↓ ALSA ↓ ASoC ↓ Machine Driver ↓ DAI Link ↓ CPU DAI ↓ DMA ↓ I2S / TDM ↓ Codec DAI ↓ DAPM ↓ DAC ↓ Speaker

如果这一章真正理解了,后面再看 Qualcomm Audio,就不会再觉得那些模块像一锅煮熟的面条。

一、先从最简单的场景开始

假设我们现在有一台非常朴素的设备:

SoC │ CPU DAI │ I2S │ ↓ Codec │ DAC │ ↓ Speaker

用户执行:

aplay test.wav

我们的目标就是:

test.wav ↓ PCM 数据 ↓ SoC ↓ I2S ↓ Codec ↓ 模拟信号 ↓ Speaker

注意一个非常重要的概念:

aplay并不是直接操作 Codec。

它也不知道你的 Codec 是什么型号。

甚至它根本不关心你后面是:

WM8960 ES8316 NAU8822 某个 Qualcomm Codec

aplay只关心一件事:

“给我一个可以播放 PCM 数据的 ALSA PCM 设备。”

至于后面怎么把数据送到 Speaker,是内核音频框架的事情。

二、第一站:aplay

先看用户空间。

执行:

aplay test.wav

aplay首先要打开一个 PCM 播放设备。

可以粗略理解成:

aplay │ ↓ 打开 PCM │ ↓ 设置参数 │ ↓ 写入 PCM 数据 │ ↓ 开始播放

典型的操作包括:

snd_pcm_open(); snd_pcm_hw_params(); snd_pcm_writei(); snd_pcm_prepare(); snd_pcm_start();

不同版本和实现细节会有所区别,这里只关注整体流程。

例如test.wav是:

Sample Rate : 48000 Hz Channels : 2 Format : S16_LE

那么应用就会告诉 ALSA:

我要: 48 kHz 2 Channel 16 bit

三、WAV 文件和 PCM 到底是什么关系?

这里顺便解决一个小白特别容易混淆的问题。

WAV 是一种文件格式。

里面通常包含:

文件头 + PCM 音频数据

例如:

test.wav ┌──────────────────┐ │ WAV Header │ ├──────────────────┤ │ PCM Data │ │ PCM Data │ │ PCM Data │ │ ... │ └──────────────────┘

aplay读取 WAV 文件后,真正送入 ALSA 的核心数据是:

PCM samples

比如:

100 230 -120 500 ...

当然真实数据会按照采样格式组织。

所以最终:

WAV 文件 ↓ 解析 ↓ PCM 数据 ↓ ALSA

四、第二站:ALSA PCM

进入内核以后,首先会经过 ALSA PCM 子系统。

这里的 PCM 可以理解成:

Linux 对音频数据流的一套标准接口。

应用看到的是:

/dev/snd/...

以及:

PCM Playback PCM Capture

例如:

aplay -l

可能看到:

card 0: MySoundCard device 0: My PCM

这时候我们可以认为:

ALSA ↓ 发现了一张 Sound Card ↓ 发现了一个 PCM Playback Device

五、这里千万不要把 ALSA 和 ASoC 混为一谈

这是初学者非常容易搞混的地方。

可以简单理解:

用户空间 ↓ ALSA API ↓ ---------------- ALSA Core ---------------- ↓ ASoC ↓ CPU / Codec / Machine

ALSA 是整个 Linux 音频框架的一部分。

ASoC 是 Linux 针对 SoC 音频硬件的一套框架。

所以:

ASoC ⊂ ALSA

用这个思路理解会简单很多。

六、第三站:ASoC

现在问题来了:

ALSA 收到 PCM 数据之后,怎么知道应该送到哪个硬件?

这时候 ASoC 开始工作。

ASoC 早已经通过前面几章介绍的东西建立好了硬件关系:

Sound Card │ ↓ DAI Link │ ┌──┴──┐ ↓ ↓ CPU Codec DAI DAI

也就是说:

aplay ↓ ALSA ↓ 找到 PCM ↓ ASoC ↓ 找到对应的 DAI Link

于是音频数据有了下一站。

七、第四站:DAI Link

前面第七章我们专门讲过:

snd_soc_dai_link

现在终于派上用场了。

假设:

CPU DAI = cpu-i2s0 Codec DAI = codec-hifi

Machine Driver 建立:

cpu-i2s0 │ │ DAI Link ↓ codec-hifi

所以当用户播放:

aplay test.wav

ASoC 知道:

这个 PCM ↓ 应该走这个 DAI Link ↓ CPU DAI + Codec DAI

这就是 Machine Driver 前面辛辛苦苦“牵线搭桥”的意义。

八、第五站:hw_params()

真正开始播放之前,有一个非常重要的步骤:

hw_params()

它负责告诉硬件:

这次播放到底使用什么参数。

例如:

48 kHz 2 Channel 16 bit

这时候链路上的各个组件需要根据这些参数进行配置。

可以粗略理解成:

ALSA ↓ ASoC ↓ DAI Link ↓ CPU DAI ↓ Codec DAI

大家开始讨论:

“这次是 48k 还是 44.1k?”

“几个声道?”

“数据是 16bit 还是 24bit?”

“接口是 I2S 还是其他格式?”

这就是hw_params()这类回调发挥作用的地方。

九、为什么hw_params()失败以后就播放不了了?

例如应用想播放:

192 kHz

但是 Codec 根本不支持。

那么在参数配置阶段就可能失败。

日志中可能看到:

hw_params failed

或者:

Invalid argument

最终:

aplay ↓ 失败

所以:

播放失败不一定是 DMA 的问题,也不一定是 Codec 坏了。

可能在很早的:

hw_params()

阶段就已经失败了。

十、第六站:set_fmt()

接下来还有一个非常重要的概念:

set_fmt()

它负责配置数字音频接口的格式。

例如:

I2S Left Justified DSP_A DSP_B

以及时钟主从关系:

CPU Master Codec Slave

或者:

CPU Slave Codec Master

还可能涉及:

BCLK LRCLK

等极性和时序配置。

十一、为什么 I2S 配错会出现“有数据但没声音”?

比如 CPU 认为:

I2S

Codec 却按照:

DSP_A

来解析。

那么可能出现:

CPU:101010101... Codec:?????

数据确实从 CPU 出来了。

时钟甚至也有。

但是 Codec 根本没按照正确方式理解这些数据。

结果:

软件:播放成功 硬件:完全听不懂 用户:没声音

这类问题在 Audio 调试中非常经典。

十二、第七站:Clock

音频系统非常依赖时钟。

常见的就有:

MCLK BCLK LRCLK

简单理解:

MCLK ↓ Codec / Audio Clock BCLK ↓ Bit Clock LRCLK ↓ Left / Right Channel Clock

例如:

48 kHz

意味着:

LRCLK

通常与 48 kHz 的采样节奏相关。

而:

BCLK

则与:

采样率 × 声道数 × 每个 sample 的 bit 数

等因素相关。

具体硬件还可能存在额外时钟关系,所以这里不要死记一个公式就认为所有平台都完全一样。

十三、音频为什么这么喜欢“时钟”?

因为音频是一个非常讲究节奏的东西。

你不能:

今天送 48000 个 sample 明天送 100 个 后天突然送 2 亿个

否则声音就开始:

“哒哒哒——呜——咔——”

所以 Audio Hardware 非常依赖稳定的:

Clock

后面你进入 Qualcomm Audio,会发现:

Clock PLL MCLK BCLK LRCLK Sample Rate

这些词出现频率非常高。

十四、第八站:DMA

参数都配置好以后,真正的 PCM 数据要开始搬运。

问题来了:

假设:

48 kHz 2 Channel 16 bit

音频数据会源源不断产生。

如果每次数据都让 CPU:

读取 ↓ 复制 ↓ 发送 ↓ 等待

那 CPU 会非常忙。

所以一般会使用:

DMA

也就是:

让 DMA 控制器帮 CPU 搬数据。

简单画:

CPU │ 配置 DMA │ ↓ ┌───────────┐ │ DMA │ └───────────┘ │ │ ↓ ↓ PCM Buffer Audio HW

CPU 负责安排工作。

DMA 负责大量重复搬运。

十五、DMA 为什么需要 Buffer?

因为音频数据是连续流。

不能:

sample ↓ 硬件 ↓ sample ↓ 硬件

每一个 sample 都让 CPU 参与一次。

通常会准备一块:

PCM Buffer

例如:

┌───────────────────────────┐ │ PCM PCM PCM PCM PCM PCM │ │ PCM PCM PCM PCM PCM PCM │ └───────────────────────────┘ ↑ DMA

DMA 不断从 Buffer 中读取数据。

Buffer 消耗到一定程度后:

DMA ↓ 产生中断 ↓ 软件继续准备数据

然后继续播放。

十六、这里出现一个很重要的概念:Period

ALSA PCM Buffer 通常还会进一步划分为:

Buffer ├── Period ├── Period ├── Period └── Period

例如:

Buffer Size = 4096 frames Period Size = 1024 frames

可以粗略理解:

整个 Buffer ┌──────┬──────┬──────┬──────┐ │ 1024 │ 1024 │ 1024 │ 1024 │ └──────┴──────┴──────┴──────┘

DMA 每处理完一个 Period,可能产生一次中断。

所以你以后看到:

buffer_size period_size period_bytes

不要害怕。

它们都是在描述:

“音频数据准备多少、多久通知软件一次。”

十七、第九站:CPU DAI

现在:

PCM Buffer ↓ DMA ↓ CPU DAI

CPU DAI 是 SoC 侧的数字音频接口。

它可能对应:

I2S TDM

或者平台上的其他音频接口。

CPU DAI 主要负责:

采样参数 接口格式 时钟 数据传输

等相关配置和操作。

十八、第十站:I2S / TDM

假设硬件采用 I2S:

CPU DAI │ │ I2S ↓ Codec DAI

这里传输的是:

数字音频数据

注意:

这里还是 0 和 1。

Speaker 现在还没有真正收到模拟声音。

十九、I2S 到底传了什么?

初学阶段可以简单理解为三类信号:

BCLK LRCLK DATA

例如:

BCLK ───────────────────── LRCLK ────────┐ ┌───── │ │ └──────┘ DATA 101010101010101010...

其中:

DATA

传输音频数据。

BCLK

提供 bit 级别的时钟。

LRCLK

帮助区分左右声道。

具体接口时序会根据协议模式不同而变化。

二十、第十一站:Codec DAI

数据到达 Codec:

I2S ↓ Codec DAI

Codec DAI 收到的是数字音频数据。

然后继续往 Codec 内部走:

Codec DAI ↓ DAC

二十一、DAC 做什么?

DAC:

Digital-to-Analog Converter

就是:

数字转模拟。

之前的数据:

101010101010...

还是数字。

经过 DAC:

Digital ↓ DAC ↓ Analog

才变成模拟音频信号。

当然,真实 Codec 内部远比这复杂,但入门阶段抓住这个核心即可。

二十二、第十二站:DAPM

这里又轮到我们第六章学过的:

DAPM

出场了。

Codec 内部可能有:

DAC Mixer PGA Speaker Output

例如:

DAC ↓ Mixer ↓ PGA ↓ SPKOUT

DAPM 会根据:

当前使用的音频路径

判断哪些 Widget 需要工作。

例如播放 Speaker:

DAC ↓ Mixer ↓ PGA ↓ Speaker

那么这条路径需要被打开。

二十三、DAPM 就像“自动开关电工”

可以把 DAPM 想象成一个非常勤快的电工。

当你播放:

Speaker

它发现:

DAC ↓ Mixer ↓ PGA ↓ Speaker

需要工作。

于是:

DAC ON Mixer ON PGA ON

而:

ADC Mic

如果完全没有使用,就不需要跟着一起上电。

这就是 DAPM 的价值之一:

只让真正需要的音频路径工作。

二十四、Mixer 也可能让你“有数据但没声音”

比如路径:

DAC ↓ Mixer ↓ Speaker

但是 Mixer:

DAC → Speaker = OFF

那么:

DMA:我在搬数据啊! CPU DAI:我在发数据啊! Codec DAI:我收到数据了啊!

最后:

Speaker:我不知道你们在忙什么。

于是:

没有声音。

所以 Audio 调试时,Mixer Control 是非常重要的。

二十五、第十三站:PGA 和 Volume

Codec 内部可能还有:

PGA

以及:

Volume Gain Mute

例如:

DAC ↓ Mixer ↓ PGA ↓ Speaker

如果:

Volume = 0

或者:

Mute = ON

结果依然可能是:

播放正常 但是没声音

所以:

“数据流通了”不等于“人耳能听到”。

二十六、第十四站:功放 PA

现实中的手机/开发板可能还不止 Codec。

例如:

Codec ↓ External PA ↓ Speaker

PA:

Power Amplifier

负责把音频信号进一步放大。

于是完整链路可能变成:

CPU ↓ I2S ↓ Codec ↓ DAC ↓ Mixer ↓ PGA ↓ PA ↓ Speaker

如果 PA 没打开:

前面全部正常 ↓ 最后 ↓ 没声音

这种情况也非常常见。

二十七、终于到了 Speaker

现在终于可以把整个过程画出来:

用户空间 │ ↓ aplay │ ↓ ALSA PCM API │ ↓ ALSA │ ↓ ASoC │ ↓ Sound Card │ ↓ DAI Link / \ ↓ ↓ CPU DAI Codec DAI │ │ │ ↓ │ DAC │ ↓ │ Mixer │ ↓ │ PGA │ ↓ └──I2S──→ PA ↓ Speaker

这就是一条非常典型的播放链路。

二十八、那么aplay成功到底说明了什么?

这是非常值得注意的问题。

假设:

aplay test.wav

没有报错。

只能说明:

至少软件播放流程中的某些环节已经成功。

不能直接证明:

Speaker 一定有声音。

因为后面还有很多可能出问题的地方:

Clock DMA I2S Codec DAPM Mixer Volume Mute PA Speaker

所以:

aplay 成功

和:

Speaker 有声音

不是完全等价的。

二十九、没有声音时应该怎么查?

不要一上来:

“Codec 坏了!”

这属于典型的“拍脑袋调音频”。

正确方式应该沿着链路一层一层排查。

1. 先确认声卡存在

aplay -l

如果连声卡都没有:

Machine Driver Device Tree CPU DAI Codec Driver DAI Link

优先检查这些地方。

2. 确认 PCM 能否打开

例如:

aplay -D hw:0,0 test.wav

如果这里直接失败:

Invalid argument

重点看:

hw_params

3. 看 Sample Rate

例如:

48 kHz

确认:

CPU DAI Codec DAI

是否支持。

4. 看 DAI Format

确认:

I2S DSP_A DSP_B

是否匹配。

5. 看 Clock

检查:

MCLK BCLK LRCLK

是否正常。

6. 看 DMA

确认:

DMA Buffer Period

有没有正常工作。

7. 看 DAPM

确认:

DAC Mixer PGA Speaker

相关路径有没有真正打开。

8. 看 Mixer

例如:

amixer -c 0

检查:

Mute Volume Switch

9. 看 Codec Register

如果前面的都没问题,再去看:

Codec Register

确认:

Power DAC Mixer Volume Mute

等配置是否正确。

三十、为什么我们要花这么大篇幅理解这条链?

因为以后你看日志时,脑子里应该自动有一条路线。

比如看到:

hw_params failed

你就知道:

PCM 参数配置阶段

看到:

set_fmt failed

你就知道:

DAI 格式/时序相关

看到:

DMA timeout

就应该想到:

DMA / Buffer / Hardware

看到:

DAPM

就想到:

音频路径 / Power / Widget

看到:

Codec

就想到:

寄存器 / DAC / Mixer / Volume

这就是我们学习 ASoC 的目的。

不是为了背:

struct snd_soc_dai_link

里面到底有多少成员。

而是为了建立:

看到一个问题,就知道它大概率在哪一层。

三十一、把整个播放过程压缩成一张图

最后再看一次。

┌──────────────────────┐ │ Application │ │ aplay │ └──────────┬───────────┘ │ ↓ ┌──────────────────────┐ │ ALSA PCM │ └──────────┬───────────┘ │ ↓ ┌──────────────────────┐ │ ASoC │ │ Sound Card │ └──────────┬───────────┘ │ ↓ ┌──────────────────────┐ │ DAI Link │ └───────┬───────┬──────┘ │ │ ↓ ↓ CPU DAI Codec DAI │ │ │ ↓ │ DAC │ ↓ │ Mixer │ ↓ │ PGA │ ↓ └──→ PA ↓ Speaker

数据传输则大致是:

PCM Buffer ↓ DMA ↓ CPU DAI ↓ I2S/TDM ↓ Codec DAI ↓ DAC ↓ Analog ↓ Speaker

而控制这条链路的,则是:

Device Tree ↓ Machine Driver ↓ Sound Card ↓ DAI Link

Codec 内部的路径管理,则主要交给:

DAPM

这样一来,前面七章的知识终于全部串起来了。

三十二、到这里,传统 ASoC 基础基本闭环

现在我们已经知道:

ALSA

解决用户空间和 PCM 接口。

ASoC

解决 SoC 音频设备的组织。

Machine Driver

负责组装整台设备。

Device Tree

描述硬件和连接关系。

DAI Link

连接 CPU DAI 和 Codec DAI。

CPU DAI

负责 SoC 侧数字音频接口。

Codec DAI

负责 Codec 侧数字音频接口。

DAPM

负责 Codec 内部音频路径和动态电源管理。

最后:

DAC ↓ PA ↓ Speaker

把数字世界里的 PCM 数据变成我们真正能听到的声音。

三十三、下一步:为什么 Qualcomm Audio 又突然复杂起来了?

到这里你可能会产生一个疑问:

既然 ASoC 已经能完成 CPU → Codec → Speaker,为什么 Qualcomm Audio 还要搞那么多东西?

比如以后你会看到:

Audio HAL PAL DSP ADSP AFE ASM ADM GPR APR COPP Copp MI2S TDM

第一眼看起来像:

“Linux Audio DLC:地狱难度版”

其实它们是在解决更复杂的问题。

因为现代 Qualcomm 平台的音频通常已经不是:

CPU ↓ I2S ↓ Codec

这么简单。

更典型的思路可能是:

Application ↓ Audio Framework ↓ Audio HAL ↓ DSP ↓ AFE / ASM / ADM ↓ Audio Interface ↓ Codec ↓ Speaker

也就是说:

真正复杂的地方,从“CPU 怎么连接 Codec”,开始逐渐转向“音频数据到底应该交给谁处理”。

这也是下一阶段最值得学习的内容。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询