news 2026/8/25 21:49:22

ALSA 实战入门:从 aplay、arecord 到 PCM 播放(二)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ALSA 实战入门:从 aplay、arecord 到 PCM 播放(二)

目录

一、先准备一台 Linux 机器

二、先看看系统到底有没有声卡

三、什么是 Card?

四、什么是 Device?

五、hw:0,0 到底是什么意思?

六、为什么叫 hw?

七、plughw:0,0 是什么?

八、aplay -l 和 aplay -L 到底有什么区别?

九、举个例子就明白了

十、default 又是什么?

十一、最简单的播放方式

十二、指定具体声卡播放

十三、为什么调试音频时经常使用 aplay?

十四、arecord 是干什么的?

十五、播放和录音其实就是两个方向

Playback

Capture

十六、ALSA PCM API 长什么样?

十七、第一个 ALSA 程序

十八、第一步:snd_pcm_open()

十九、第二步:设置 PCM 参数

二十、第三步:真正把参数交给硬件

二十一、第四步:prepare

二十二、第五步:snd_pcm_writei()

二十三、为什么这里不是 write()?

二十四、Frame 和 snd_pcm_writei() 的关系

二十五、ALSA 播放流程总结

二十六、那 aplay 和我们写的程序有什么关系?

二十七、为什么 aplay 能播放 WAV,却不能直接播放 MP3?

二十八、这一篇最重要的几张图

1. ALSA 播放

2. Card 和 Device

3. hw:0,0

4. PCM API

二十九、遇到“没有声音”,先别急着改代码

三十、下一篇开始进入 ALSA Kernel


系列说明:
这是《从零理解 Linux ALSA、ASoC 到 Qualcomm Audio》系列的第二篇。

第一篇我们认识了 PCM、ALSA、Codec 和 ASoC。

这一篇不继续“背概念”,直接上手 Linux 音频。

我们重点搞清楚几个问题:

  • aplay -l到底在看什么?

  • aplay -Laplay -l有什么区别?

  • hw:0,0到底是什么意思?

  • plughw:0,0又是什么?

  • ALSA 播放 PCM 的基本流程是什么?

  • snd_pcm_open()snd_pcm_hw_params()snd_pcm_writei()又在干什么?

把这些搞明白,后面再看 ALSA Kernel 和 ASoC 就不会那么容易迷路。

一、先准备一台 Linux 机器

本文使用 Linux 环境进行演示。

Ubuntu、Debian、开发板 Linux 都可以。

先检查系统有没有 ALSA 工具:

aplay --version

如果能看到版本信息,说明基本没问题。

如果没有安装,可以根据发行版安装 ALSA 工具。

Ubuntu / Debian 通常可以安装:

sudo apt install alsa-utils

安装完成后,可以检查:

aplay --version arecord --version amixer --version

如果都能正常输出版本信息,就可以继续了。

二、先看看系统到底有没有声卡

执行:

aplay -l

注意这里是小写字母:

-l

不是数字1

如果系统识别到了播放设备,可能看到类似:

**** List of PLAYBACK Hardware Devices **** card 0: Device [USB Audio], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0

不同机器的结果当然不一样。

比如开发板可能看到:

card 0: xxx

PC 可能看到:

card 0: PCH card 1: HDMI

USB 声卡可能又是另外一种名字。

这里最重要的不是记住具体名字。

而是先搞明白:

card device

这两个东西。

三、什么是 Card?

card可以简单理解为:

ALSA 眼中的一张声卡。

例如:

card 0

表示:

第 0 张声卡

如果机器上有两张声卡:

card 0 card 1

也就很好理解了。

比如一台电脑可能同时存在:

card 0 → 主板声卡 card 1 → HDMI 音频

具体是什么设备,需要看实际系统。

四、什么是 Device?

再看:

device 0

它表示这张声卡上的一个音频设备。

可以先粗略理解成:

Card 0 ├── Device 0 ├── Device 1 └── Device 2

所以:

card 0

和:

device 0

不是一回事。

这一点非常重要。

以后你在日志里看到:

card 0, device 0

不要把它理解成:

“这是一个设备,名字叫 0。”

实际上是:

Card 0 ↓ Device 0

五、hw:0,0到底是什么意思?

如果你以后学习 ALSA,十有八九会看到:

hw:0,0

第一次看到可能会想:

这是什么神秘字符串?

其实非常简单。

拆开:

hw:0,0

可以理解为:

hw: card 0 device 0

也就是:

硬件声卡 0 硬件设备 0

所以:

hw:0,0

对应:

Card 0 Device 0

如果是:

hw:1,0

就是:

Card 1 Device 0

如果是:

hw:0,1

就是:

Card 0 Device 1

这下是不是简单多了?

六、为什么叫 hw?

hw可以理解成:

Hardware

也就是:

直接访问硬件 PCM 设备。

例如:

aplay -D hw:0,0 test.wav

意思就是:

使用 Card 0、Device 0 这个硬件 PCM 设备播放test.wav

但是这里有一个坑。

hw对格式要求比较严格。

例如硬件只支持:

48000 Hz 2 Channels 16 bit

你却要求:

44100 Hz 2 Channels 16 bit

那么可能直接失败。

这时候就轮到:

plughw

登场了。

七、plughw:0,0是什么?

先看:

plughw:0,0

它和:

hw:0,0

最大的区别,可以简单理解成:

hw ↓ 尽量直接使用硬件

而:

plughw ↓ 允许 ALSA 做一些格式转换 ↓ 再交给硬件

例如:

应用 ↓ 44100 Hz ↓ plughw ↓ 转换 ↓ 48000 Hz ↓ 硬件

具体能转换什么,取决于 ALSA 的插件和硬件能力。

所以:

hw

更接近:

“我就按你的硬件要求来,别给我乱改。”

而:

plughw

更像:

“你这个格式和硬件不完全匹配?行,我帮你转换一下。”

当然,这只是帮助理解的比喻。

八、aplay -laplay -L到底有什么区别?

这是 ALSA 初学者非常容易搞混的一对命令。

先执行:

aplay -l

它主要查看:

硬件播放设备。

再执行:

aplay -L

它查看的是:

ALSA 提供的 PCM 设备名称/PCM 定义。

两者不是一个维度。

可以简单记:

aplay -l ↓ 我机器上有哪些真实的播放硬件?

而:

aplay -L ↓ ALSA 给我提供了哪些可以使用的 PCM 名称?

九、举个例子就明白了

执行:

aplay -l

可能看到:

card 0: PCH device 0: Analog device 1: HDMI

这说明:

Card 0 ├── Device 0 → Analog └── Device 1 → HDMI

但是:

aplay -L

可能出现更多东西:

default sysdefault front surround21 surround40 hw:CARD=PCH,DEV=0 plughw:CARD=PCH,DEV=0 ...

为什么-L看起来比-l复杂很多?

因为:

-l

主要是在看:

实际硬件设备。

而:

-L

是在看:

ALSA 用户空间可以使用的 PCM 名称和插件。

因此不要看到aplay -L输出一大堆名字就开始怀疑人生。

它们很多并不是一张张新的声卡。

十、default又是什么?

你可能还会看到:

default

例如:

aplay -D default test.wav

这里的default不是:

默认声卡就是 Card 0。

它实际上是 ALSA 提供的一个默认 PCM 配置。

它可能通过 ALSA 配置,把音频最终路由到某个实际设备。

所以:

default

和:

hw:0,0

不是一回事。

可以先简单理解:

hw:0,0 ↓ 我明确指定硬件设备 default ↓ 你按照系统默认配置帮我找设备

后面学习 ALSA 配置文件时,会再深入讲default

十一、最简单的播放方式

假设当前目录有:

test.wav

直接执行:

aplay test.wav

如果系统默认音频设备配置正确,那么:

🎵

应该就出来了。

这里发生了什么?

可以简单理解为:

test.wav ↓ aplay ↓ ALSA ↓ default PCM ↓ 真实硬件 PCM ↓ 声卡 ↓ Speaker

所以aplay其实就是一个非常方便的 ALSA PCM 播放程序。

十二、指定具体声卡播放

假设:

Card 0 Device 0

那么可以:

aplay -D hw:0,0 test.wav

如果希望使用 ALSA 的格式转换能力:

aplay -D plughw:0,0 test.wav

所以:

hw:0,0

和:

plughw:0,0

在实际调试中非常常见。

尤其当你怀疑:

到底是应用的问题,还是硬件 PCM 的问题?

可以直接绕过很多上层组件,用aplay测一下。

十三、为什么调试音频时经常使用 aplay?

因为它非常简单。

假设 Android Audio 播放不了声音。

你可能要排查:

AudioTrack ↓ AudioFlinger ↓ Audio Policy ↓ Audio HAL ↓ DSP ↓ ALSA ↓ Codec ↓ Speaker

这一长串东西。

看得脑壳疼。

但是如果你能直接:

aplay -D hw:0,0 test.wav

并且成功播放。

那么至少可以说明:

底层 ALSA PCM 播放链路具备正常工作的可能。

如果aplay都无法播放,那就没有必要第一时间怀疑 AudioTrack。

这就是为什么:

ALSA 命令行工具是音频工程师排查问题时非常重要的工具。

十四、arecord 是干什么的?

既然:

aplay

负责播放。

那么:

arecord

就是:

录音。

例如:

arecord -l

查看录音设备。

如果找到:

card 0 device 0

可以尝试:

arecord -D hw:0,0 test.wav

然后对着麦克风说:

你好,ALSA。

再按:

Ctrl + C

结束录音。

然后:

aplay test.wav

如果能听到刚才录下来的声音:

🎤 → 💾 → 🔊

恭喜。

你已经完成了一次完整的:

Capture → Playback

十五、播放和录音其实就是两个方向

现在可以把 ALSA PCM 理解成两个方向。

Playback

Application ↓ ALSA ↓ PCM Buffer ↓ Hardware ↓ Speaker

Capture

Microphone ↓ Hardware ↓ PCM Buffer ↓ ALSA ↓ Application

所以:

Playback

就是:

数据往硬件走。

而:

Capture

就是:

数据从硬件回来。

十六、ALSA PCM API 长什么样?

前面一直在使用:

aplay

但是aplay本身也是一个程序。

那么我们自己写程序,能不能直接使用 ALSA?

当然可以。

ALSA 提供了 PCM API。

最简单的播放流程可以理解成:

打开设备 ↓ 设置参数 ↓ 准备设备 ↓ 写入 PCM 数据 ↓ 关闭设备

对应的 API 大概是:

snd_pcm_open() snd_pcm_hw_params() snd_pcm_prepare() snd_pcm_writei() snd_pcm_close()

这几个函数先记住。

后面看aplay源码时,就会发现:

原来aplay也是通过这些 API 在干活。

十七、第一个 ALSA 程序

下面写一个非常简单的 ALSA PCM 播放程序。

先说明一下:

这个例子为了学习 ALSA API,代码进行了简化。真正工程中的错误处理、参数协商、XRUN 恢复等内容会复杂一些。

代码如下:

#include <stdio.h> #include <stdlib.h> #include <alsa/asoundlib.h> int main(void) { snd_pcm_t *pcm; snd_pcm_hw_params_t *params; int ret; unsigned int rate = 48000; int channels = 2; ret = snd_pcm_open( &pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0 ); if (ret < 0) { fprintf(stderr, "无法打开 PCM 设备: %s\n", snd_strerror(ret)); return 1; } snd_pcm_hw_params_alloca(&params); snd_pcm_hw_params_any(pcm, params); snd_pcm_hw_params_set_access( pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED ); snd_pcm_hw_params_set_format( pcm, params, SND_PCM_FORMAT_S16_LE ); snd_pcm_hw_params_set_channels( pcm, params, channels ); snd_pcm_hw_params_set_rate_near( pcm, params, &rate, 0 ); ret = snd_pcm_hw_params(pcm, params); if (ret < 0) { fprintf(stderr, "无法设置硬件参数: %s\n", snd_strerror(ret)); snd_pcm_close(pcm); return 1; } printf("ALSA PCM 设备打开成功\n"); printf("Rate: %u Hz\n", rate); printf("Channels: %d\n", channels); snd_pcm_prepare(pcm); /* * 这里应该不断向 PCM 设备写入音频数据。 */ snd_pcm_close(pcm); return 0; }

先不要急着研究每一行。

我们只看整个流程。

十八、第一步:snd_pcm_open()

代码:

snd_pcm_open( &pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0 );

这个函数的任务很直白:

打开一个 PCM 设备。

这里最重要的参数是:

"hw:0,0"

也就是:

Card 0 Device 0

以及:

SND_PCM_STREAM_PLAYBACK

表示:

我要播放。

如果是录音,则会使用:

SND_PCM_STREAM_CAPTURE

所以:

snd_pcm_open()

可以先理解成:

“ALSA,我要打开这个 PCM 设备。”

十九、第二步:设置 PCM 参数

接下来:

snd_pcm_hw_params_any(pcm, params);

可以理解成:

先获取一份这个 PCM 设备的参数配置。

然后设置:

访问方式 格式 声道 采样率

例如:

snd_pcm_hw_params_set_format( pcm, params, SND_PCM_FORMAT_S16_LE );

表示:

16 bit Little Endian

再比如:

snd_pcm_hw_params_set_channels( pcm, params, 2 );

表示:

2 Channels

采样率:

snd_pcm_hw_params_set_rate_near( pcm, params, &rate, 0 );

表示我们希望使用:

48000 Hz

二十、第三步:真正把参数交给硬件

前面只是:

“我想要这些参数。”

真正提交参数:

snd_pcm_hw_params(pcm, params);

可以简单理解为:

应用 ↓ 我想要: 48000 Hz 16 bit 2 Channels ↓ ALSA ↓ 检查硬件是否支持 ↓ 设置 PCM

如果硬件完全不支持这些参数,就可能返回错误。

这就是为什么 ALSA 调试时经常会看到:

Invalid argument

或者:

Sample format not available

之类的错误。

很多时候就是:

你要求的参数,硬件不接受。

二十一、第四步:prepare

设置好参数之后:

snd_pcm_prepare(pcm);

可以简单理解为:

让 PCM 设备进入准备播放的状态。

到这里:

PCM 设备 ↓ 打开了 ↓ 参数设置好了 ↓ 准备好了

下一步就可以开始送数据。

二十二、第五步:snd_pcm_writei()

真正播放 PCM 数据时,最核心的函数之一就是:

snd_pcm_writei()

例如:

snd_pcm_writei( pcm, buffer, frames );

它可以简单理解成:

把 PCM 数据写进 ALSA。

例如:

PCM 数据 ↓ snd_pcm_writei() ↓ ALSA PCM Buffer ↓ DMA ↓ 硬件

所以如果你以后在源码里看到:

snd_pcm_writei()

脑子里可以直接翻译成:

“往播放设备送 PCM 数据。”

二十三、为什么这里不是 write()?

你可能会问:

Linux 不是有write()吗?为什么 ALSA 要搞一个snd_pcm_writei()

因为音频不是简单地:

写几个字节

PCM 有自己的概念:

Sample Frame Period Buffer

而 ALSA PCM API 需要处理:

  • 采样率

  • 声道

  • Frame

  • Buffer

  • Period

  • 硬件状态

所以它提供了专门的 PCM API。

这也是为什么后面学习 ALSA Kernel 时,我们会看到大量 PCM 相关的数据结构。

二十四、Frame 和snd_pcm_writei()的关系

这里再把前面的 Frame 拉回来。

假设:

16 bit 2 Channels

那么一个 Frame:

Left = 16 bit Right = 16 bit

也就是:

4 Byte

如果:

snd_pcm_writei(pcm, buffer, 1024);

这里的:

1024

通常表示:

写入 1024 个 Frame。

而不是:

写入 1024 Byte。

这一点非常重要。

所以 ALSA PCM 编程时,一定要区分:

Byte Sample Frame

否则很容易把 Buffer 大小算错。

二十五、ALSA 播放流程总结

现在把程序重新整理一下:

snd_pcm_open() ↓ 打开 PCM ↓ snd_pcm_hw_params() ↓ 设置采样率、格式、声道等 ↓ snd_pcm_prepare() ↓ 准备播放 ↓ snd_pcm_writei() ↓ 不断写入 PCM 数据 ↓ 硬件播放 ↓ snd_pcm_close() ↓ 关闭 PCM

这就是一个非常典型的 ALSA PCM Playback 流程。

二十六、那 aplay 和我们写的程序有什么关系?

现在再回头看:

aplay test.wav

是不是突然感觉没那么神秘了?

aplay本质上也是一个 ALSA 用户空间程序。

它里面也需要完成类似的工作:

打开 PCM ↓ 获取/设置参数 ↓ 准备 PCM ↓ 读取音频数据 ↓ 写入 PCM ↓ 处理播放状态 ↓ 关闭 PCM

所以:

aplay

可以看成:

一个已经帮你把 ALSA PCM 播放流程封装好的工具。

我们自己写程序,就是把这个过程拆出来看。

二十七、为什么aplay能播放 WAV,却不能直接播放 MP3?

这个问题也很有意思。

因为:

WAV

通常可以直接包含 PCM 数据。

而:

MP3

是压缩编码格式。

所以:

MP3 ↓ MP3 Decoder ↓ PCM ↓ ALSA ↓ Speaker

而 WAV 如果本身就是 PCM:

WAV ↓ PCM ↓ ALSA ↓ Speaker

这就是为什么:

ALSA 更关心的是 PCM,而不是 MP3、AAC 这些上层音频编码格式。

这点对于理解 Android Audio 非常重要。

Android 上层可能处理:

MP3 AAC FLAC Opus

但最终到了底层播放设备,通常还是要变成 PCM 数据。

二十八、这一篇最重要的几张图

如果你只想记重点,记下面这几张。

1. ALSA 播放

Application ↓ ALSA Library ↓ ALSA PCM ↓ Kernel ↓ Hardware ↓ Speaker

2. Card 和 Device

Card 0 ├── Device 0 ├── Device 1 └── Device 2

3.hw:0,0

hw:0,0 │ │ │ └── Device 0 └──── Card 0

4. PCM API

snd_pcm_open() ↓ snd_pcm_hw_params() ↓ snd_pcm_prepare() ↓ snd_pcm_writei() ↓ snd_pcm_close()

把这几张图理解了,这篇文章的目标基本就完成了。

二十九、遇到“没有声音”,先别急着改代码

音频开发有一个非常经典的场景:

程序运行正常 日志也没有报错 但是: 没声音。

这时候很多人第一反应:

“是不是 Audio HAL 有问题?”

然后一头扎进几万行代码。

其实没必要。

可以先从最底层开始测试。

例如:

aplay -l

确认播放设备存在。

然后:

aplay -D hw:0,0 test.wav

如果不行,再尝试:

aplay -D plughw:0,0 test.wav

然后查看:

amixer

检查相关 Mixer 控件。

还可以查看内核日志:

dmesg | grep -i audio

或者:

dmesg | grep -i snd

当然,实际平台的调试方法会更加复杂。

但是一个基本原则值得记住:

音频问题最好按照调用链一层一层排查,而不是上来就猜。

三十、下一篇开始进入 ALSA Kernel

到这里,我们已经站在用户空间看完了 ALSA。

现在可以继续往下钻。

下一篇开始进入真正的 Linux 内核:

Application ↓ ALSA Library ↓ ────────────── Kernel ────────────── ↓ ALSA Core ↓ PCM Core ↓ Sound Driver ↓ Hardware

我们会重点回答几个问题:

ALSA Kernel 到底是什么? snd_card 是干什么的? snd_pcm 又是什么? snd_pcm_ops 里面为什么有: .open() .hw_params() .prepare() .trigger() .pointer() aplay 执行以后, 这些函数到底是怎么被调用起来的?

再往下一步,就会正式进入 ASoC:

ALSA ↓ ASoC ├── CPU DAI ├── Codec DAI ├── Machine Driver └── DAPM

到那时候,我们就开始真正接触 Linux 音频驱动的核心内容了。

先把aplay -laplay -Lhw:0,0plughw:0,0这几个东西搞明白。后面看 ASoC,会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 21:41:22

节温器生产中小厂家技术创新:三个核心方向对比

浙江专业的节温器生产厂家是专注汽车售后替换节温器产品研发生产的制造主体&#xff0c;国内这类厂家多集中在汽摩配产业集群区域。以瑞安市亚奇汽车零部件有限公司为例&#xff0c;其坐落于有“中国汽摩配之都”之称的温州瑞安市塘下镇&#xff0c;是典型的本土成长型节温器生…

作者头像 李华
网站建设 2026/8/25 21:38:46

【Kubernetes 实战】(一)部署 Ruoyi-Cloud —— 环境准备

Kubernetes v1.28.2 集群安装文档&#xff08;CentOS 7&#xff09; 目录 一、环境准备二、安装容器运行时&#xff08;containerd&#xff09;三、安装 kubeadm、kubelet、kubectl四、初始化控制平面节点&#xff08;Master&#xff09;五、安装 Calico 网络插件六、配置 ku…

作者头像 李华
网站建设 2026/8/25 21:34:47

Celery系列-05-生产实践与源码阅读

文章目录从“能运行”到“能生产”还差什么&#xff1f;使用 Celery Beat 执行周期任务为什么同一套计划通常只能运行一个 Beat&#xff1f;为什么需要多个队列&#xff1f;Worker 并发池怎么选择&#xff1f;PreforkEventlet / GeventSoloThreads并发数不是越大越好理解 Prefe…

作者头像 李华
网站建设 2026/8/25 21:31:02

企微SCRM怎么选:别只看大厂标签,先核这5项

先别急着问大厂还是小厂企微SCRM选型最容易被一个外壳带偏&#xff1a;看品牌体量&#xff0c;而不是看系统能不能进业务现场。对多数企业来说&#xff0c;真正难的不是拿到一份功能清单&#xff0c;而是把客户承接、标签分层、群发触达、客服协同、订单会员打通、数据复盘&…

作者头像 李华
网站建设 2026/8/25 21:28:58

UE5.8程序化植被编辑器(PVE)实战:3分钟快速生成自然森林场景

大家好&#xff0c;我是专注于游戏开发技术分享的博主。在虚幻引擎5&#xff08;UE5&#xff09;的生态中&#xff0c;程序化内容生成&#xff08;PCG&#xff09;正成为提升开发效率、丰富场景细节的利器。特别是对于开放世界、大型场景中的植被布置&#xff0c;手动摆放不仅耗…

作者头像 李华