Linux ALSA / ASoC子系统实战笔记
2026/7/6 1:52:32 网站建设 项目流程

Linux ALSA / ASoC 子系统实战笔记

面向场景:RK3588S 运动相机,数字 PDM MEMS 麦克风直连 SoC 的 PDM 接口,采集 48kHz/16bit 双声道,存 WAV/MP3,和视频同步。
硬件链路真相:麦克风既不是自己从零写的声卡,也不是USB 声卡,而是PDM 数字麦 → RK3588 PDM 控制器 → DMA → 内存,软件走 ASoC 现成的三层框架
阅读定位:你已掌握其它子系统,ALSA 是新的。
本文原则:三层框架只讲够你配对设备树、看懂/proc/asound和排错的程度,重心在你真正要动手的两件事——设备树配置、用户空间采集。


0. 先建立正确的心智模型(和 USB 那份是同一套路)

在你项目里,音频这条链路的真相是:

  • 你不写 codec 驱动。PDM 数字麦没有控制总线(只有时钟+数据),内核里通用的dmic-codec就是给它准备的,现成的。
  • 你不写平台驱动。RK3588 的 PDM 控制器驱动rockchip-pdm也是现成的。
  • 你不写机器驱动。用内核通用的simple-audio-card把两者一绑就行。
  • 你真正要做的是两件事:
    1. 设备树:使能 PDM 控制器、声明 dmic codec、用simple-audio-card把它们连成一条声卡;
    2. 用户空间:arecord采集,或用 alsa-lib 写采集循环,再编码成 WAV/MP3、和视频同步。

"理解 ASoC 三层"的唯一目的,是让你(a) 把设备树的 CPU/Codec/Machine 三部分配对,(b) 当没声音时看懂/proc/asounddmesg定位到底哪层断了。仅此而已——不需要读懂三层的内部实现。


1. ALSA 全景:它到底提供了什么

ALSA(Advanced Linux Sound Architecture)是 Linux 的音频子系统,分内核侧用户侧两半:

内核空间

用户空间

/dev/snd/*

你的应用 / arecord / ffmpeg

alsa-lib
libasound: snd_pcm_* 等 API

ALSA Core
PCM / Control / Timer 框架

ASoC 三层
Machine + Platform + Codec

硬件:PDM 控制器 + MEMS 麦

ALSA 对你暴露的能力主要三块(你项目用到的是前两块):

能力是什么你用它做什么
PCM数字音频的播放/采集通道采集麦克风数据(核心)
Control / Mixer各种开关、增益、通道选择(kcontrol)调增益、选通道(PDM 麦控制项通常很少)
Timer / MIDI定时、乐器接口用不到

声卡在系统里长什么样

一块声卡在 ALSA 里是Card → Device → Subdevice三级:

  • Card(声卡):一整套音频硬件,编号card0card1
  • Device(设备):声卡上的一个 PCM 通道,编号device0(即hw:0,0里的第二个 0)。
  • 你在文件系统里能直接看到它:
ls/dev/snd/# controlC0 —— card0 的控制接口(mixer)# pcmC0D0c —— card0 device0 capture(采集)★你要的# pcmC0D0p —— card0 device0 playback(播放)cat/proc/asound/cards# 0 [rockchippdmmi]: ... - rockchip,pdm-mic ← 你的 PDM 声卡arecord-l# 列出所有"采集"设备,最重要的一条命令# card 0: rockchippdmmi [rockchip,pdm-mic], device 0: ...# Subdevices: 1/1

hw:0,0记法:hw:<card>,<device>。你采集命令里天天用它指定"从哪块声卡的哪个设备录"。arecord -l告诉你 card/device 号是多少。


2. ★核心:ASoC 三层架构(理解到"能配设备树"即可)

普通 PC 声卡用 ALSA 原生驱动,但嵌入式音频硬件是拼出来的(SoC 的音频接口 + 一颗外部 codec + 板级连线各不相同)。ASoC(ALSA System on Chip)就是为嵌入式设计的框架,把一块声卡拆成三个可复用的部分:

一块 ASoC 声卡

DAI Link 绑定

DAI Link 绑定

Machine 机器层
板级胶水:把下面两个绑起来

Platform 平台层
= CPU DAI + DMA/PCM
SoC 侧的音频接口

Codec 编解码层
外部音频器件

三层各是什么(用你的硬件对号入座)

职责你项目里是谁你要不要写
Codec(编解码)描述外部音频器件的能力、控制项、音频通路dmic-codec(内核通用 PDM 数字麦 codec)❌ 现成
Platform(平台)SoC 侧:CPU DAI(音频接口) + DMA 搬运(PCM)rockchip-pdm(RK3588 PDM 控制器驱动)❌ 现成
Machine(机器)板级胶水:声明"哪个 CPU DAI 接哪个 Codec DAI",设置格式/时钟主从simple-audio-card(内核通用机器驱动)❌ 用配置代替代码

三层全是现成的——这正是"走 ASoC 现成三层框架"的含义。你的工作从"写三个驱动"退化成"在设备树里把三层配对"。

两个必须懂的名词:DAI 和 DAI Link

  • DAI(Digital Audio Interface,数字音频接口):芯片间传音频数据的物理链路(I2S、TDM、PDM等)。SoC 侧的叫CPU DAI,codec 侧的叫Codec DAI
  • DAI Link:把一个 CPU DAI 和一个 Codec DAI连成一条通路。Machine 层的核心工作就是声明 DAI Link。

你项目里的那条 DAI Link 就是:rockchip-pdm(CPU DAI)—— dmic-codec(Codec DAI),由simple-audio-card声明。

DAPM(动态音频电源管理)——了解它存在即可

DAPM会根据当前有没有音频在跑,自动开关各个音频"部件(widget)“的电源、连通音频通路。对 PDM 麦这种简单场景,你几乎不需要碰 DAPM。你只要知道:如果将来遇到"通路没打开导致没声音”,可能和 DAPM 的 widget/route 没连有关。现在跳过

本节你只需记住三句话:

  1. ASoC 把声卡拆成 Machine + Platform + Codec 三块;
  2. 你的三块分别是simple-audio-card+rockchip-pdm+dmic-codec,全现成;
  3. 你的活是在设备树里把它们配对,不是写代码。

3. 为什么 PDM 麦用dmic-codec?(理解这个,就理解了整条链路)

这是你场景里最该弄明白的一点,否则设备树里 codec 那段会看得莫名其妙。

PDM(Pulse Density Modulation)数字 MEMS 麦的物理特性:

  • 只有两根信号线:一根时钟(CLK)、一根数据(DATA)
  • 它输出的是1-bit 高速脉冲密度流(不是 PCM),数据密度代表声音幅度。
  • 它没有 I2C/SPI 这类控制总线——你无法给它发寄存器配置,它就是"给时钟就吐数据"。
  • 一根时钟线可以挂两个麦:两个麦分别在时钟的上升沿和下降沿输出,这样一对 CLK/DATA 就能采集左右两个声道(这正是你要的"双声道"的一种典型实现)。

PDM → PCM 的解调(抽取)由谁做?RK3588 的 PDM 控制器做:它接收 1-bit 脉冲流,做抽取滤波(decimation),转成正常的 PCM 采样(比如 48kHz/16bit),再通过 DMA 送到内存。所以:

CLK + DATA

DMA

PDM MEMS 麦
(1-bit 脉冲流)

RK3588 PDM 控制器
抽取滤波 → PCM

内存
(48kHz/16bit PCM)

你的应用 arecord

现在回答"为什么用 dmic-codec":

  • ASoC 的模型要求一条 DAI Link必须有两端:一个 CPU DAI + 一个 Codec DAI。
  • 但 PDM 麦没有可控制的 codec 芯片——它太"哑"了,没有寄存器可配。
  • 于是内核提供了一个**"占位用"的通用 codec 驱动dmic-codec**:它不控制任何实际硬件,只是向 ASoC声明"这一端是个数字麦,支持采集、这些采样率/格式",好让 DAI Link 能成立。
  • 真正的信号处理(抽取)在 CPU DAI 侧(rockchip-pdm)完成。

一句话:dmic-codec是 PDM 麦在 ASoC 框架里的"替身",满足"DAI Link 要有 codec 一端"的形式要求,本身不干活。理解这点,设备树里 codec 那段就顺理成章了。


4. ★实战第一步:设备树配置(你的主战场)

你要在板级设备树里写/改三段,正好对应三层。以 RK3588S +simple-audio-card为例。

4.1 使能 PDM 控制器(对应 Platform / CPU DAI)

PDM 控制器节点通常已在 SoC 的.dtsi里定义好,你在板级.dts里把它使能并配好引脚:

&pdm0 { status = "okay"; /* 关键:#sound-dai-cells 让别处能用 sound-dai 引用它 */ #sound-dai-cells = <0>; /* 配置 PDM 用到的引脚:1 根时钟 + 需要的数据线 */ pinctrl-names = "default"; pinctrl-0 = <&pdmm0_clk /* PDM 时钟 */ &pdmm0_sdi0>; /* 数据线 0(挂 1~2 个麦→1~2 声道) */ /* 若要更多声道,再加 &pdmm0_sdi1 等,以你的原理图为准 */ /* 有的 BSP 需要显式声明用哪几路 sdi;以 Rockchip 文档/dtsi 注释为准 */ rockchip,path-map = <0>; /* 示意:仅供参考,按实际 BSP 语法 */ };

要点:

  • #sound-dai-cells = <0>必须的,否则simple-audio-card无法用sound-dai = <&pdm0>引用它。
  • 引脚(pinctrl)是最容易配错的地方。PDM 至少要 1 根 CLK + 1 根 SDI 数据线。你要双声道:最省引脚的做法是一根 SDI 上挂两个麦(时钟双沿采样);若原理图是两根 SDI 各挂一个麦,则要把两根数据线都在 pinctrl 里列出。务必对着相机原理图确认 PDM 数据线接在哪个 GPIO、挂了几个麦。

4.2 声明 dmic codec(对应 Codec 层)

新增一个dmic-codec节点。它没有寄存器、不接总线,就是个纯声明:

/ { dmic_codec: dmic-codec { compatible = "dmic-codec"; #sound-dai-cells = <0>; /* 可选属性(按需):告诉框架有几个声道、上电稳定延时等 */ num-channels = <2>; /* 双声道 */ wakeup-delay-ms = <50>; /* 麦上电到数据稳定的等待,消除开头爆音 */ }; };

num-channelswakeup-delay-msdmic-codec支持的可选属性:前者声明声道数,后者给麦一点上电稳定时间(实践中能避免录音开头一小段噪声)。

4.3 用 simple-audio-card 把两层绑成一块声卡(对应 Machine 层)

这是把 CPU DAI 和 Codec DAI连成 DAI Link的地方——你不写机器驱动,用这段配置代替:

/ { pdm_sound: pdm-sound { compatible = "simple-audio-card"; simple-audio-card,name = "rockchip,pdm-mic"; /* 声卡名,会显示在 /proc/asound/cards */ /* 一条 DAI Link:CPU 端 = PDM 控制器,Codec 端 = dmic-codec */ simple-audio-card,cpu { sound-dai = <&pdm0>; /* 引用 4.1 的 PDM 控制器 */ }; simple-audio-card,codec { sound-dai = <&dmic_codec>; /* 引用 4.2 的 dmic codec */ }; }; };

三段配完,一块名为rockchip,pdm-mic的采集声卡就诞生了。重启后arecord -l应能看到它。

simple-audio-card,cpu → sound-dai

simple-audio-card,codec → sound-dai

引脚

pdm-sound
(simple-audio-card / Machine)

&pdm0
(rockchip-pdm / CPU DAI)

&dmic_codec
(dmic-codec / Codec)

pdmm0_clk + pdmm0_sdi0

4.4 关于 Rockchip BSP 的一个提醒

RK3588 + 6.1 内核大概率是Rockchip 的 BSP 内核。它除了标准simple-audio-card,可能还提供厂商机器驱动(如rockchip,rk3588-audio-*rockchip,multicodecs-card)。两者都能用:

  • 优先用simple-audio-card:它是主线标准,你学到的是可迁移到任何平台的通用知识,也最好排错。
  • 如果 Rockchip SDK 的参考 DTS 用的是厂商机器驱动,照它的语法配也行,原理完全一样(还是把 CPU DAI 和 codec 绑一条 DAI Link)。

4.5 内核配置(menuconfig)确认选上

CONFIG_SND=y CONFIG_SND_SOC=y # ASoC 框架 CONFIG_SND_SOC_ROCKCHIP=y # Rockchip 音频平台支持 CONFIG_SND_SOC_ROCKCHIP_PDM=y # ★ RK3588 PDM 控制器驱动(平台层) CONFIG_SND_SOC_DMIC=y # ★ 通用 PDM 数字麦 codec(dmic-codec) CONFIG_SND_SIMPLE_CARD=y # ★ simple-audio-card 机器驱动

三个打星的,对应你的三层,缺一层声卡就起不来。


5. ★实战第二步:用户空间采集

设备树把声卡做出来后,剩下的就是"怎么录"。这部分和普通 ALSA 采集完全一样。

5.1 先确认声卡在、参数对

# 1) 采集设备列表:确认 card/device 号arecord-l# card 0: pdmmic [rockchip,pdm-mic], device 0: ...# 2) ★极其有用:查这块声卡到底支持哪些采样率/格式/声道arecord-Dhw:0,0 --dump-hw-params# 重点看输出里的:# FORMAT: S16_LE S24_LE S32_LE ← 支持哪些位深# CHANNELS: [2 2] ← 支持几声道# RATE: [8000 48000] ← 支持的采样率范围

--dump-hw-params是排 PDM 问题的第一命令。PDM 控制器原生输出的位深不一定是 16bit——很多 PDM 抽取后是S24_LE 或 S32_LE。如果你直接-f S16_LE报 “Sample format non available”,就是因为硬件这条链路那一刻不支持 S16,得先看--dump-hw-params给的实际支持列表(见第 7 节坑点)。

5.2 采集(对应项目的 48kHz/16bit 双声道)

# 录 10 秒,48kHz,16bit 小端,双声道,存 WAVarecord-Dhw:0,0-fS16_LE-r48000-c2-d10test.wav# 边录边看电平(确认真的有信号进来,不是静音)arecord-Dhw:0,0-fS16_LE-r48000-c2-Vstereo /dev/null# -V stereo 会画出左右声道的实时电平条,对着麦说话应看到跳动

参数速查:-D设备(hw:0,0)、-f格式(S16_LE)、-r采样率、-c声道数、-d时长、-t容器格式(wav/raw)。

5.3 采集成 WAV / MP3(呼应项目"双格式录音")

WAV:arecord默认就是 WAV,如上。

MP3:arecord不直接出 MP3,两种做法:

# 做法一:arecord 出裸流 → 管道给 lame 编码arecord-Dhw:0,0-fS16_LE-r48000-c2-traw|\lame-r-s48--bitwidth16-mj - out.mp3# -r 表示输入是 raw PCM,-s 48 采样率(kHz),-m j 联合立体声# 做法二:直接用 ffmpeg 从 ALSA 采集并编码(最省事,也便于同时出多格式)ffmpeg-falsa-ac2-ar48000-ihw:0,0-c:alibmp3lame-b:a192k out.mp3

在相机固件里,通常不用命令行,而是在你的采集程序里拿到 PCM 后调用编码库(lame/ffmpeg 的 libavcodec)编成 MP3,并和视频一起封进 MP4。命令行是用来先验证链路通不通的。

5.4 用 alsa-lib API 写采集循环(你程序里真正要写的)

命令行验证通了之后,固件里用libasound采集。核心就一个"配置 → 循环读"的骨架:

#include<alsa/asoundlib.h>// 1. 打开采集设备snd_pcm_t*pcm;snd_pcm_open(&pcm,"hw:0,0",SND_PCM_STREAM_CAPTURE,0);// 2. 配置硬件参数(采样率/格式/声道)snd_pcm_hw_params_t*hw;snd_pcm_hw_params_alloca(&hw);snd_pcm_hw_params_any(pcm,hw);snd_pcm_hw_params_set_access(pcm,hw,SND_PCM_ACCESS_RW_INTERLEAVED);snd_pcm_hw_params_set_format(pcm,hw,SND_PCM_FORMAT_S16_LE);// 位深snd_pcm_hw_params_set_channels(pcm,hw,2);// 双声道unsignedintrate=48000;snd_pcm_hw_params_set_rate_near(pcm,hw,&rate,0);// 采样率snd_pcm_hw_params(pcm,hw);// 应用// 3. 循环读取 PCM 数据(每次读一批帧)shortbuf[1024*2];// 1024 帧 × 2 声道while(recording){intframes=snd_pcm_readi(pcm,buf,1024);if(frames==-EPIPE){// overrun:内核缓冲被灌满没及时取走snd_pcm_prepare(pcm);// 恢复后继续continue;}// 把 buf 交给:写 WAV / 送 MP3 编码器 / 送 MP4 封装...encode_and_mux(buf,frames);}// 4. 收尾snd_pcm_drain(pcm);snd_pcm_close(pcm);

要点:

  • 交织(interleaved)访问:双声道数据在 buffer 里是L R L R ...交替排列(SND_PCM_ACCESS_RW_INTERLEAVED),取数据时按这个布局解析左右声道。
  • -EPIPE(overrun)必须处理:采集是内核往缓冲区填、你来取。你取慢了缓冲区满,就 overrun。要snd_pcm_prepare恢复。这是采集程序稳定性的关键。
  • 编译链接:gcc xxx.c -lasound

6. 音视频同步(呼应项目,点到为止)

你项目是相机,音频最终要和视频一起封进 MP4 并对齐。这块严格说超出"ALSA 应用"本身,但给你实用思路:

  • 拿采集时间戳:ALSA 提供硬件时间戳,可用snd_pcm_status_get_htstamp()/snd_pcm_status_get_audio_htstamp()获取每批采样对应的时刻,用来和视频帧的 PTS 对齐。
  • 实践上更常见的是用现成封装框架:直接用ffmpeg(libav*)GStreamer同时接 ALSA 音频源和 V4L2 视频源,让框架统一打时间戳、做 A/V 同步、封 MP4。你只管把两路源喂进去。
  • 对齐的本质:音频按采样率天然是等间隔时钟(48kHz 就是每秒 48000 样),视频帧率可能抖动;通常以音频时钟为基准,视频帧按其到达/采集时刻对齐到音频时间轴。

一句话:别自己造 A/V 同步轮子,用 ffmpeg/GStreamer 把 ALSA 采集源和 V4L2 视频源一起管起来。


7. PDM 特有的坑(这些最能体现你真懂)

现象原因 / 解法
位深不匹配-f S16_LE报 “format non available”PDM 抽取常原生输出S24_LE/S32_LE。先--dump-hw-params看实际支持,用支持的格式录,或在应用里做 S32→S16 转换
声道数不对明明双声道,却只有 1 声道或全是一个麦双声道要么"一根 SDI 双沿采两个麦",要么"两根 SDI 各一个麦"。pinctrl 数据线数量原理图实际接法必须一致
采样率被拒设 44.1kHz 起不来,48kHz 却行PDM 采样率由麦时钟频率 ÷ 抽取比决定,不是任意值。48kHz/16kHz 这类是标配,先用标配验证
录音开头有爆音/噪声每段录音前 100ms 有杂音麦上电到数据稳定需要时间。设wakeup-delay-ms,或应用里丢弃开头一小段
有 DC 偏置 / 低频嗡嗡波形整体偏移或有低频噪声PDM 抽取器一般带高通滤波去 DC;若仍有,检查控制器高通配置或在应用侧加高通
完全静音但不报错能录、文件正常大小、但全 0多半是引脚没配对(CLK/SDI 接错 GPIO)或麦没供电。先-V stereo看电平,再示波器量 PDM 时钟有没有出、数据线有没有翻转

8. 排错速查表(没声音时按这个顺序查)

自底向上,逐层排除——这正是"理解三层"的价值所在:

# 第1步:声卡到底注册出来了吗?(Machine 层是否成功绑定)cat/proc/asound/cards arecord-l# → 看不到你的声卡:说明 simple-audio-card 没绑成功。# 查 dmesg 里 asoc 报错,通常是设备树三段某处 sound-dai 引用错、# 或 #sound-dai-cells 漏了、或某层驱动没编进内核。# 第2步:内核初始化音频时报什么错?dmesg|grep-Ei'asoc|pdm|dmic|rockchip.*(pcm|dai|card)'# → 看 CPU DAI(pdm)、codec(dmic)、machine(simple-card)分别有没有 probe 成功。# 哪个没出现或报错,就是哪一层断了。# 第3步:声卡在,但录出来是静音 → 硬件/引脚层arecord-Dhw:0,0-fS16_LE-r48000-c2-Vstereo /dev/null# → 电平条不动:软件通了但没信号进来。# ① 示波器量 PDM CLK 引脚有没有时钟输出(没有→控制器/pinctrl 问题)# ② 量 DATA 引脚有没有随声音翻转(没有→麦没供电/接错/坏)# ③ 核对设备树 pinctrl 的 GPIO 与原理图是否一致# 第4步:看这块声卡的运行时信息cat/proc/asound/card0/pcm0c/sub0/hw_params# 录音时看当前实际参数cat/proc/asound/card0/pcm0c/sub0/status# 看流状态、是否 overrun

记住这个层次映射——它把抽象的"ASoC 三层"变成了可操作的排错路径:

排错发现对应哪一层往哪查
arecord -l看不到声卡Machine(simple-audio-card)设备树三段的绑定、#sound-dai-cells、内核选项
dmesg 里 pdm probe 失败Platform(rockchip-pdm)PDM 节点使能、时钟、pinctrl
dmesg 里 dmic 相关报错Codec(dmic-codec)dmic-codec 节点、CONFIG_SND_SOC_DMIC
声卡在但静音硬件/引脚原理图接法、GPIO、麦供电、示波器

9. 用你已掌握的子系统来类比(快速上手)

你已会的ASoC 里的对应说明
设备树使能控制器 + pinctrl使能 PDM 节点 + 配 CLK/SDI 引脚完全同构,只是换了个控制器
I2C/SPI 从设备节点挂在总线下codec 节点(不过 PDM 麦无总线,用 dmic-codec 占位)关键差异:PDM 麦没有控制总线
compatible匹配驱动"dmic-codec"/"rockchip,rk3588-pdm"匹配思路一样
MFD 一个芯片挂多个子设备一块声卡由 Machine 绑 CPU DAI + Codec 两"半"Machine 像"把两半拼成整体"的胶水
DMA 把外设数据搬进内存Platform 层的 PCM 就是音频 DMAPDM 抽取后经 DMA 进 DDR,你arecord从这取

一句话总结你在 ALSA/ASoC 上的工作量:设备树里配三段(PDM 控制器 / dmic-codec / simple-audio-card)把声卡拼出来,用户空间用arecord或 alsa-lib 采集再编码封装。三层驱动全是现成的,你做的是配对排错,不是写驱动。


附:面试可能追问的几个点(备着)

  • “PDM 麦为什么要用 dmic-codec,它不控制硬件还要它干嘛?”→ ASoC 的 DAI Link 必须有 codec 一端,而 PDM 麦无控制总线、太"哑",所以用通用dmic-codec做占位声明其采集能力,真正的 PDM→PCM 抽取在 CPU DAI(rockchip-pdm)侧完成。
  • “一根 PDM 时钟怎么采两个声道?”→ 两个麦分别在时钟上升沿/下降沿输出,一对 CLK/DATA 即可采左右声道。
  • “ASoC 三层各是谁、你写了哪层?”→ Machine=simple-audio-card、Platform=rockchip-pdm、Codec=dmic-codec,全现成;我的工作是在设备树里把三层配对成一块声卡,并做采集应用。
  • “没声音你怎么定位?”→ 自底向上:先arecord -l看声卡在不在(Machine),再 dmesg 看各层 probe(Platform/Codec),再-V stereo+ 示波器看有没有真实信号(硬件/引脚)。
  • “你说的’PDM 直连 SoC’和 USB 声卡、板载 codec 有什么区别?”→ USB 声卡整套音频硬件在 USB 设备里、走 UAC 类驱动;板载模拟 codec(如 ES8388)通过 I2C 控制 + I2S 传数;而 PDM 数字麦最简单,无控制总线,只有 CLK/DATA,由 SoC 的 PDM 控制器直接抽取,codec 层用 dmic-codec 占位。

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

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

立即咨询