目录
一、先从最简单的场景开始
二、第一站: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 Codecaplay只关心一件事:
“给我一个可以播放 PCM 数据的 ALSA PCM 设备。”
至于后面怎么把数据送到 Speaker,是内核音频框架的事情。
二、第一站:aplay
先看用户空间。
执行:
aplay test.wavaplay首先要打开一个 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 / MachineALSA 是整个 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-hifiMachine Driver 建立:
cpu-i2s0 │ │ DAI Link ↓ codec-hifi所以当用户播放:
aplay test.wavASoC 知道:
这个 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 认为:
I2SCodec 却按照:
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 HWCPU 负责安排工作。
DMA 负责大量重复搬运。
十五、DMA 为什么需要 Buffer?
因为音频数据是连续流。
不能:
sample ↓ 硬件 ↓ sample ↓ 硬件每一个 sample 都让 CPU 参与一次。
通常会准备一块:
PCM Buffer例如:
┌───────────────────────────┐ │ PCM PCM PCM PCM PCM PCM │ │ PCM PCM PCM PCM PCM PCM │ └───────────────────────────┘ ↑ DMADMA 不断从 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 DAICPU 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 DAICodec 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 ↓ SPKOUTDAPM 会根据:
当前使用的音频路径判断哪些 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 ↓ SpeakerPA:
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_params3. 看 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 Switch9. 看 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 LinkCodec 内部的路径管理,则主要交给:
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”,开始逐渐转向“音频数据到底应该交给谁处理”。
这也是下一阶段最值得学习的内容。