【嵌入式Linux - 应用开发】音频(ALSA 框架) - 指南
【目录】
一、【库介绍】ALSA 框架概述(Linux 系统的标准音频框架,是驱动 + API + 工具的完整音频子系统)
二、【库介绍】alsa-lib 库概述(ALSA 的用户空间库中的一套 C 语言 API)
1. PCM(Pulse Code Modulation)接口:用于音频数据的采集(录音)和播放
2. Mixer(混音器)接口:用于音量调节、静音、输入源选择
3. Control(控制)接口:对声卡设备的管理和参数查询
三、【库介绍】alsa-lib 库移植以及 alsa-utils 工具的使用
(一) alsa-lib 库移植:一般移植 alsa-lib 和 alsa-utils 两个库
1. aplay:用于播放音频文件,支持 WAV、RAW 等 PCM 格式。
2. arecord:用于录音测试,支持 WAV 与 RAW 格式。
3. amixer:是命令行混音器,用于查看和设置声卡控件。
4. alsamixer:提供交互式界面,方便调节音量和输入输出源。
5. alsactl:用于保存和恢复声卡配置,实现音量与路由的持久化。
四、【基础知识】音频子系统/音频设备架构详解(音频设备、I2S & ALSA)
(一) 什么是音频设备?实现声音采集、处理与播放的一套硬件或虚拟装置,如声卡+麦克风+扬声器等
(二) 完整的音频硬件架构(音频系统通常由输入端(麦克风/线路)、编解码器、数字接口、处理单元和输出端(扬声器/耳机)组成)
【补充】硬件层面的音频接口(包括 I2S、USB Audio 等,用于传输数字或模拟音频信号)
二、SPDIF(Sony/Philips Digital Interface)
五、【开发基础】ASLA 框架驱动部分的 sound 设备节点 & ALSA 框架驱动的声卡与设备
2. PCM 设备节点命名规则(pcmC[卡号]D[设备号][p/c])
(二) 【核心】ALSA 框架驱动中的声卡、设备、PCM 设备
1. Card(声卡,音频编解码器):/proc/asound/cardX/
2. Device(设备号,功能单元/所有声卡注册的设备):/proc/asound/devices
3. PCM Device(所有 PCM 设备):/proc/asound/pcm
3. Buffer/Period/Frame/Sample 关系图
(十) Over Run 和 Under Run(XRUN)
1. snd_pcm_open - 打开一个 PCM 音频设备。
2. snd_pcm_close - 关闭 PCM 设备,释放相关资源。
3. snd_pcm_drain - 等待所有待处理的音频帧播放完毕。
4. snd_pcm_drop - 立即停止 PCM 设备,丢弃所有待处理的音频帧。
5. snd_pcm_prepare - 准备 PCM 设备用于数据传输,将 PCM 状态设置为 PREPARED。
6. snd_pcm_reset - 立即停止 PCM,将 PCM 状态重置为 PREPARED。
7. snd_pcm_pause - 暂停或恢复 PCM 设备。
8. snd_pcm_resume - 从挂起状态恢复 PCM 设备。
1. snd_pcm_hw_params_malloc - 为硬件参数结构分配内存。
2. snd_pcm_hw_params_free - 释放硬件参数结构的内存。
3. snd_pcm_hw_params_any - 初始化硬件参数为设备支持的完整配置空间。
4. snd_pcm_hw_params - 将配置好的硬件参数应用到 PCM 设备。
5. snd_pcm_hw_params_set_access - 设置访问类型(交错/非交错)。
6. snd_pcm_hw_params_set_format - 设置音频数据格式。
7. snd_pcm_hw_params_set_rate - 设置采样率。
8. snd_pcm_hw_params_set_rate_near - 设置最接近的采样率。
9. snd_pcm_hw_params_set_channels - 设置声道数。
10. snd_pcm_hw_params_set_period_size - 设置周期大小(每个中断的帧数)。
11. snd_pcm_hw_params_set_period_size_near - 设置最接近的周期大小。
12. snd_pcm_hw_params_set_periods - 设置周期数(buffer 中包含的周期数)。
13. snd_pcm_hw_params_set_buffer_size - 设置 buffer 大小(总帧数)。
14. snd_pcm_hw_params_get_period_size - 获取周期大小。
15. snd_pcm_hw_params_get_buffer_size - 获取 buffer 大小。
1. snd_pcm_sw_params_malloc - 为软件参数结构分配内存。
2. snd_pcm_sw_params_free - 释放软件参数结构的内存。
3. snd_pcm_sw_params_current - 获取当前软件参数配置。
4. snd_pcm_sw_params - 将软件参数应用到 PCM 设备。
5. snd_pcm_sw_params_set_start_threshold - 设置自动启动的阈值。
6. snd_pcm_sw_params_set_stop_threshold - 设置自动停止的阈值。
7. snd_pcm_sw_params_set_avail_min - 设置可用空间的最小值,用于唤醒等待的应用程序。
1. snd_pcm_writei - 向 PCM 设备写入交错模式的音频数据(播放)。
2. snd_pcm_writen - 向 PCM 设备写入非交错模式的音频数据。
3. snd_pcm_readi - 从 PCM 设备读取交错模式的音频数据(录音)。
4. snd_pcm_readn - 从 PCM 设备读取非交错模式的音频数据。
5. snd_pcm_wait - 等待 PCM 设备变为可读或可写状态。
1. snd_pcm_state - 获取 PCM 设备的当前状态。
3. snd_pcm_avail_update - 更新并获取当前可用的帧数。
4. snd_pcm_delay - 获取硬件延迟(尚未播放/录制的帧数)。
1. snd_async_add_pcm_handler - 为 PCM 设备添加异步回调处理器。
2. snd_async_handler_get_pcm - 从异步处理器获取 PCM 句柄。
1. snd_mixer_open - 打开一个 Mixer 设备。
2. snd_mixer_close - 关闭 Mixer 设备。
3. snd_mixer_attach - 将 Mixer 附加到声卡。
4. snd_mixer_detach - 从声卡分离 Mixer。
5. snd_mixer_load - 加载 Mixer 元素。
6. snd_mixer_selem_register - 注册简单元素类。
7. snd_mixer_first_elem - 获取第一个 Mixer 元素。
8. snd_mixer_elem_next - 获取下一个 Mixer 元素。
9. snd_mixer_selem_get_name - 获取简单元素的名称。
10. snd_mixer_selem_has_playback_volume - 检查元素是否有播放音量控制。
11. snd_mixer_selem_get_playback_volume - 获取播放音量值。
12. snd_mixer_selem_set_playback_volume - 设置播放音量值。
13. snd_mixer_selem_set_playback_volume_all - 设置所有声道的播放音量。
14. snd_mixer_selem_get_playback_volume_range - 获取播放音量范围。
15. snd_mixer_selem_set_playback_switch - 设置播放开关(静音/非静音)。
16. snd_mixer_selem_set_playback_switch_all - 设置所有声道的播放开关。
1. snd_strerror - 将错误码转换为可读的错误描述字符串。
2. snd_pcm_format_size - 获取指定格式的样本大小(字节数)。
3. snd_pcm_format_physical_width - 获取指定格式的物理位宽。
4. snd_pcm_format_width - 获取指定格式的有效位宽。
5. snd_pcm_bytes_to_frames - 将字节数转换为帧数。
6. snd_pcm_frames_to_bytes - 将帧数转换为字节数。
3. snd_ctl_card_info - 获取声卡信息。
5. snd_card_get_name - 获取声卡名称。
6. snd_card_get_longname - 获取声卡完整名称。
1. snd_pcm_recover - 从错误状态恢复 PCM 设备。
十、【开发记录】PCM 录音详解(录制 PCM 数据,得到 PCM 文件)
十一、【开发优化】异步方式编程(PCM 播放/录音)(参考:pcm_*_async.c)
(一) 【前言】数据传输流程(播放/录音):应用程序把音频数据写入内核,内核处理传入硬件(这个过程需要时间)
十二、【开发优化】Poll 方式编程(PCM 播放/录音)(参考:pcm_*_poll.c)
十三、【开发记录】PCM 设备状态管理(参考:pcm_playback_ctl.c)
十四、【开发记录】混音器(Mixer)控制:音频设备的控制接口,用于管理声卡的各种音频参数和设置(参考:pcm_playback_mixer.c)
【 参考资源】
教程主要参考正点原子
- ALSA 官方文档:
- ALSA Library API:
https://www.alsa-project.org/alsa-doc/alsa-lib/
- Linux音频子系统:Documentation/sound/
https://my.oschina.net/emacs_8852579/blog/17425686
【总结】
(一) ALSA 中的"音频设备"= 一套完整的音频子系统
- 音频设备:音频子系统
✅ 正确理解:
音频设备(声卡)= 麦克风 + 喇叭 + CODEC芯片 +
I2S控制器 + 驱动程序 +
放大器 + 混音器 + ...
这是一套完整的、协同工作的系统!
————————————————————————————————————————————————————
i.MX6U + WM8960 音频子系统:
硬件:
├─ i.MX6U的I2S控制器(CPU侧)
├─ WM8960 CODEC芯片
├─ DAC(数模转换)
├─ ADC(模数转换)
├─ 放大器
├─ 麦克风接口
├─ 喇叭接口
└─ 耳机插孔
软件:
├─ 三个驱动程序(platform + codec + machine)
└─ ALSA设备节点(/dev/snd/pcmC0D0p等)
这整个系统 = 一个声卡(Card 0)
ALSA中的"声卡"也是同样道理!
- 声卡:音频编解码器+PCM 播放/录音硬件
- (ALSA)设备
- (声卡注册)PCM 设备:与音频编解码器输出/输入 相连的麦克风/扬声器/耳机

(二) 概念区分表格
类别 | 名称 | 说明 | 例子 |
物理设备 | 输入设备 | 采集声音 | 麦克风、线路输入 |
输出设备 | 播放声音 | 喇叭、耳机 | |
编解码芯片 | 数模/模数转换 | WM8960, NAU8822 | |
传输接口 | 数字接口 | 传输数字音频 | I2S, PCM, SPDIF, USB |
模拟接口 | 传输模拟信号 | 3.5mm插孔, RCA | |
控制接口 | 配置寄存器 | I2C, SPI | |
软件设备 | 字符设备 | Linux 设备节点 |
|
ALSA 设备 | 逻辑设备名 |
| |
驱动层 | CODEC 驱动 | 控制 CODEC 芯片 | WM8960 driver |
平台驱动 | CPU 音频控制器 | i.MX SSI driver | |
声卡驱动 | 绑定 CODEC 和平台 | Machine driver |
常见说法 | 正确理解 |
"I2S 设备" | ❌ I2S 是接口/总线,不是设备 |
"I2S 声卡" | ❌ 声卡包含 CODEC + I2S 控制器 |
"USB 音频设备" | ✅ 通过 USB 接口的音频设备(如 USB 耳机) |
"ALSA 设备" | ✅ ALSA 框架管理的音频设备(软件抽象) |
"PCM 设备" | ✅ ALSA 中的播放/录音设备节点 |
"WM8960 是音频设备" | ⚠️ 准确说是"音频 CODEC 芯片" |
一、【库介绍】ALSA 框架概述(Linux 系统的标准音频框架,是驱动 + API + 工具的完整音频子系统)
(一) 什么是 ALSA?
ALSA(Advanced Linux Sound Architecture,高级 Linux 声音架构)是 Linux 系统的标准音频框架。
- ALSA 是 Linux 的 音频驱动框架,核心在内核驱动,但它同时包含用户空间的库和工具,所以不仅仅是“一个驱动”,而是 驱动 + API + 工具 的完整音频子系统。
┌─────────────────────────────────────────────────────────────┐
│ 应用程序层 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 音乐播放器 │ │ 录音软件 │ │ 游戏应用 │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
└────────┼────────────────┼────────────────┼─────────────────┘
│ │ │
└────────────────┴────────────────┘
│
┌────────────────▼────────────────┐
│ ALSA Library │ ← alsa-lib 库
│ (应用程序接口层) │
│ - PCM 接口(播放/录音) │
│ - Mixer 接口(音量控制) │
│ - Control 接口(设备控制) │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ ALSA Core (内核) │ ← 内核驱动
│ /dev/snd/* 设备节点 │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ 声卡硬件驱动程序 │ ← 硬件驱动
│ (如: WM8960, AC97等) │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ 音频硬件 │ ← 物理硬件
│ 喇叭/耳机 麦克风 │
└─────────────────────────────────┘

(二) ALSA 的三个主要组件
组件 | 说明 | 位置 |
ALSA Driver | 内核驱动程序 | Linux 内核中 |
ALSA Library | 用户空间库(alsa-lib) |
|
ALSA Utilities | 命令行工具 |
|
应用程序层
↓
ALSA Library (alsa-lib)
↓
ALSA Core (内核)
↓
声卡硬件驱动
↓
音频硬件
二、【库介绍】alsa-lib 库概述(ALSA 的用户空间库中的一套 C 语言 API)
ALSA 提供一套标准 API -> alsa-lib
(一) 什么是 alsa-lib?
alsa-lib 是 ALSA 的用户空间库,提供了一套 C 语言 API,让应用程序可以方便地访问音频设备。
(二) 主要功能模块
#include // ALSA 库头文件
这是 ALSA 用户态开发的统一入口头文件,包含了 PCM、Mixer、Control、MIDI、Timer 等接口的声明。
- 编译时需要链接 -lasound。
1. PCM(Pulse Code Modulation)接口:用于音频数据的采集(录音)和播放
PCM 数据流:
模拟音频 → ADC → PCM数字数据 → 编码 → 存储/传输
↓
解码 → PCM数字数据 → DAC → 模拟音频
- 作用:最核心的接口,用于音频数据的采集(录音)和播放。
- 数据流:
- 输入:模拟音频 → ADC → PCM 数字数据 → 编码/存储/传输
- 输出:解码 → PCM 数字数据 → DAC → 模拟音频
- 关键 API:
snd_pcm_open():打开 PCM 设备(播放/录音)。snd_pcm_hw_params_*:配置硬件参数(采样率、通道数、格式、缓冲区大小)。snd_pcm_writei()/snd_pcm_readi():写入/读取 PCM 帧。snd_pcm_prepare()、snd_pcm_start()、snd_pcm_drain():控制流状态。
- 应用场景:播放器、录音机、语音通信、音频处理前端。
2. Mixer(混音器)接口:用于音量调节、静音、输入源选择
- 作用:用于音量调节、静音、输入源选择等。
- 逻辑:Mixer 建立在 Control 接口之上,提供更高层的抽象。
- 关键 API:
snd_mixer_open()/snd_mixer_close():打开/关闭混音器。snd_mixer_attach():绑定到某个声卡。snd_mixer_selem_register():注册简单元素(如 Master、PCM、Mic)。snd_mixer_selem_set_playback_volume_all():设置音量。
- 应用场景:播放器音量调节、录音增益控制、静音开关。
3. Control(控制)接口:对声卡设备的管理和参数查询
- 作用:提供对声卡设备的管理和参数查询。
- 功能:
- 查询声卡信息(卡号、设备号、驱动名)。
- 获取/设置硬件控件(如开关、路由、增益)。
- 关键 API:
snd_ctl_open()/snd_ctl_close():打开/关闭控制接口。snd_ctl_card_info():获取声卡信息。snd_ctl_elem_read()/snd_ctl_elem_write():读写控件值。
- 应用场景:系统音频管理工具、设备枚举、参数调试。
4. 其他常用功能模块
一、Raw MIDI 接口
- 作用:直接访问声卡的 MIDI 总线,处理原始 MIDI 消息。
- 关键 API:
snd_rawmidi_open()、snd_rawmidi_read()、snd_rawmidi_write()。 - 应用场景:电子乐器、MIDI 控制器、音序器。
二、Sequencer(音序器)接口
- 作用:比 Raw MIDI 更高级,提供事件调度、时间戳、路由。
- 关键 API:
snd_seq_open()、snd_seq_event_output()。 - 应用场景:MIDI 编曲软件、虚拟乐器、复杂 MIDI 路由。
三、Timer 接口
- 作用:访问声卡或系统的定时器,用于音频事件同步。
- 关键 API:
snd_timer_open()、snd_timer_read()。 - 应用场景:音频同步、延迟测量、实时音频调度。
四、HW Dep(硬件依赖)接口
- 作用:访问声卡特定的硬件功能(非通用 API)。
- 应用场景:驱动开发、特殊硬件调试。
【总结】
- PCM 接口:核心,负责音频数据流(录音/播放)。
- Mixer 接口:音量、静音、输入源控制。
- Control 接口:设备管理与参数查询。
- Raw MIDI / Sequencer:MIDI 事件处理。
- Timer:音频同步与调度。
- HW Dep:硬件特定功能。
(三) alsa-lib 编译选项
# 编译时需要链接 alsa 库
gcc -o player player.c -lasound
# 或者使用 pkg-config
gcc -o player player.c $(pkg-config --cflags --libs alsa)
三、【库介绍】alsa-lib 库移植以及 alsa-utils 工具的使用
(一) alsa-lib 库移植:一般移植 alsa-lib 和 alsa-utils 两个库
ALSA(Advanced Linux Sound Architecture,高级 Linux 声音架构) 是 Linux 内核中的音频子系统,提供了统一的音频与 MIDI 支持。它不仅包含内核驱动,还配套提供了用户态开发库和工具,方便应用程序进行音频开发与调试。
- alsa-lib:C 语言开发库,编译音频应用程序时必须依赖。
- alsa-utils:常用工具集,包含配置与测试声卡的命令行工具。(基于 alsa-lib 库实现)
- 常见工具:
arecord:录音aplay:播放speaker-test:扬声器测试amixer:命令行混音器alsamixer:交互式混音器alsaconf:声卡配置
在系统迁移或驱动移植过程中,通常需要同时移植 alsa-lib 与 alsa-utils,以保证应用层能够正常编译和运行。
一般采用出厂系统移植好的库
(二) alas-utils 工具库使用
1. aplay:用于播放音频文件,支持 WAV、RAW 等 PCM 格式。
- 列出设备:
aplay -l查看可用播放设备。 - 播放文件:
aplay -D hw:0,0 test.wav指定声卡与设备。 - 播放 RAW 流:
aplay -t raw -r 16000 -c 1 -f S16_LE audio.pcm,需手动指定采样率、通道数和格式。 - 参数说明:
-r采样率,-c通道数,-f采样格式。
2. arecord:用于录音测试,支持 WAV 与 RAW 格式。
- 列出设备:
arecord -l查看可用录音设备。 - 录制 WAV 文件:
arecord -D hw:0,0 -r 16000 -c 1 -f S16_LE -d 5 out.wav,录制 5 秒单声道音频。 - 录制 RAW 流:
arecord -t raw -r 48000 -c 2 -f S16_LE out.pcm。 - 电平显示:
arecord -vv out.wav可实时显示输入电平。
3. amixer:是命令行混音器,用于查看和设置声卡控件。
- 查看控件:
amixer -c 0 scontrols简要列出控件,amixer -c 0 scontents显示详细内容。 - 设置音量:
amixer -c 0 sset Master 80%调整主音量。 - 静音/取消静音:
amixer -c 0 sset Master mute或unmute。 - 录音源选择:
amixer -c 0 sset 'Input Source' Mic选择麦克风作为输入源。
4. alsamixer:提供交互式界面,方便调节音量和输入输出源。
- 启动:
alsamixer或alsamixer -c 0指定声卡。 - 操作:左右键切换控件,上下键调节音量,
M静音/取消静音,Tab切换播放/录音视图。 - 用途:快速排查无声或录音失败问题,直观调整音量与增益。
5. alsactl:用于保存和恢复声卡配置,实现音量与路由的持久化。
- 保存配置:
sudo alsactl store将当前设置保存到/var/lib/alsa/asound.state。 - 恢复配置:
sudo alsactl restore在系统启动时恢复之前保存的设置。 - 自定义文件:
alsactl -f my.state store保存到指定文件,alsactl -f my.state restore恢复。 - 应用场景:部署环境或嵌入式系统中,保证开机后音量与输入输出配置保持一致。
四、【基础知识】音频子系统/音频设备架构详解(音频设备、I2S & ALSA)
(一) 什么是音频设备?实现声音采集、处理与播放的一套硬件或虚拟装置,如声卡+麦克风+扬声器等
- 音频设备:音频子系统
✅ 正确理解:
音频设备(声卡)= 麦克风 + 喇叭 + CODEC芯片 +
I2S控制器 + 驱动程序 +
放大器 + 混音器 + ...
这是一套完整的、协同工作的系统!
————————————————————————————————————————————————————
i.MX6U + WM8960 音频子系统:
硬件:
├─ i.MX6U的I2S控制器(CPU侧)
├─ WM8960 CODEC芯片
├─ DAC(数模转换)
├─ ADC(模数转换)
├─ 放大器
├─ 麦克风接口
├─ 喇叭接口
└─ 耳机插孔
软件:
├─ 三个驱动程序(platform + codec + machine)
└─ ALSA设备节点(/dev/snd/pcmC0D0p等)
这整个系统 = 一个声卡(Card 0)
ALSA中的"声卡"也是同样道理!
- 声卡:音频编解码器
- (声卡注册)PCM 设备:与音频编解码器输出/输入 相连的麦克风/扬声器/耳机

(二) 完整的音频硬件架构(音频系统通常由输入端(麦克风/线路)、编解码器、数字接口、处理单元和输出端(扬声器/耳机)组成)
以 i.MX6U + WM8960 为例:
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 音乐播放器│ │ 录音软件 │ │ 游戏 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└────────┼─────────────┼─────────────┼───────────────────────┘
│ │ │
└─────────────┴─────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ ALSA Library │
│ (用户空间 API 接口) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Linux 内核 ALSA 框架 │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ ALSA Core (核心层) │ │
│ │ - PCM 框架 │ │
│ │ - Control 框架 │ │
│ │ - Mixer 框架 │ │
│ └───────────────────┬────────────────────────────┘ │
│ │ │
│ ┌───────────────────▼────────────────────────────┐ │
│ │ 声卡驱动 (machine driver) │ │
│ │ - 绑定 CPU DAI 和 CODEC DAI │ │
│ └───────┬───────────────────────┬────────────────┘ │
│ │ │ │
│ ┌───────▼────────┐ ┌──────▼────────┐ │
│ │ CPU DAI │ │ CODEC DAI │ │
│ │ (I2S控制器) │ │ (WM8960驱动) │ │
│ └───────┬────────┘ └──────┬────────┘ │
└──────────┼───────────────────────┼──────────────────────────┘
│ │
│ I2S 总线 │
│ (数字音频传输) │
┌──────────▼───────────────────────▼──────────────────────────┐
│ 硬件层 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ i.MX6U SoC │ I2S │ WM8960 CODEC │ │
│ │ │◄───────►│ │ │
│ │ ┌───────────┐ │ 信号线 │ ┌───────────┐ │ │
│ │ │I2S控制器 │ │ BCLK │ │ DAC │ │ │
│ │ │(SAI/SSI) │ │ LRCLK │ │ (数模) ├─┼─→喇叭 │
│ │ │ │ │ SDATA │ │ │ │ │
│ │ │ + DMA │ │ MCLK │ │ ├─┼─→耳机 │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ │ │ │ │ │
│ │ ┌───────────┐ │ I2C │ ┌───────────┐ │ │
│ │ │I2C控制器 ├──┼────────►│ │ ADC │◄┼──麦克风 │
│ │ │(配置) │ │ (控制) │ │ (模数) │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ │ │ │ │ │
│ │ │ │ ┌───────────┐ │ │
│ │ │ │ │ 放大器 │ │ │
│ │ │ │ │ 混音器 │ │ │
│ │ │ │ └───────────┘ │ │
│ └─────────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘
说明:
- I2S 总线:传输 PCM 数字音频数据
- I2C 总线:配置 CODEC 芯片的寄存器(音量、采样率等)
- DAC:数模转换器(Digital to Analog Converter)
- ADC:模数转换器(Analog to Digital Converter)


【补充】I2S 与 ALSA 关系
可以理解为:
- 硬件层
- SoC 内部有 I²S 控制器(CPU DAI)。
- 外部有 Codec 芯片(带 ADC/DAC)。
- 两者通过 I²S 总线传输 PCM 数据。
- Codec 芯片输出模拟信号到扬声器灯
- 内核层(ASoC 框架)
- Platform 驱动:管理 DMA + I²S 控制器。
- Codec 驱动:管理外部 Codec 芯片(音量、增益、ADC/DAC)。
- Machine 驱动:把 I²S 控制器和 Codec 绑定,定义音频路由和时钟关系。
- ASoC Core:把这些组合成 ALSA 的“声卡”,在
/dev/snd/下导出pcmC0D0p、pcmC0D0c等节点。
- 用户空间
- 应用层通过
alsa-libAPI(snd_pcm_writei()、snd_pcm_readi())访问 PCM 设备。 - ALSA 内核驱动会把这些调用转化为 DMA 传输,最终通过 I²S 把数据送到 Codec。
- 应用层通过
【补充】硬件层面的音频接口(包括 I2S、USB Audio 等,用于传输数字或模拟音频信号)
1. I2S(Inter-IC Sound)
I2S 不是音频设备,而是一种数字音频传输接口/总线协议!
一、I2S 是什么?
I2S 是一种串行总线接口标准,用于在 CPU/SoC 和音频 CODEC 芯片之间传输数字音频数据。
┌─────────────────┐ ┌─────────────────┐
│ CPU/SoC │ I2S 总线 │ CODEC 芯片 │
│ (i.MX6U) │◄──────────────────►│ (WM8960) │
│ │ │ │
│ - I2S 控制器 │ │ - DAC/ADC │
│ - DMA │ │ - 放大器 │
│ │ │ - 混音器 │
└─────────────────┘ └─────────────────┘
I2S 总线包含以下信号线:
├── BCLK (位时钟,Bit Clock)
├── LRCLK (左右声道时钟,也叫 WS/FS - Word Select/Frame Sync)
├── SDATA (串行数据线)
└── MCLK (主时钟,可选)
二、I2S 传输示例
LRCLK: ┌─────┐ ┌─────┐ ┌─────┐
│ L │ R │ L │ R │ L │
└─────┘ └─────┘ └─────┘
BCLK: ┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐
SDATA: [左声道 16位][右声道 16位][左声道 16位]...
L = 左声道数据
R = 右声道数据
2. 其他常见音频接口
一、PCM/I2S 接口
I2S 接口 - 飞利浦标准,主要用于立体声
PCM 接口 - 主要用于语音通信(如电话)
TDM 接口 - 时分复用,支持多声道(> 2个)
二、SPDIF(Sony/Philips Digital Interface)
数字音频接口 - 光纤或同轴
用途 - 高保真音频传输
常见场景 - 家庭影院、音响系统
三、AC97/HD-Audio
AC97 - Audio Codec '97(老式标准)
HD-Audio - High Definition Audio(Intel 标准)
常见于 - PC 主板音频
四、(UAC)USB Audio
USB 音频 - 通过 USB 传输音频数据
常见设备 - USB 耳机、USB 声卡、USB 麦克风
五、【开发基础】ASLA 框架驱动部分的 sound 设备节点 & ALSA 框架驱动的声卡与设备
在 Linux 内核设备驱动层、基于 ALSA 音频驱动框架注册的 sound 设备会在/dev/snd目录下生成相应的设备节点文件
(一) 【设备节点】/dev/snd/ 目录结构以及详解
1. ALSA 设备节点
ALSA 的设备节点都在 /dev/snd/ 目录下
ALSA 在 Linux 中创建的软件设备节点(并非硬件本身)
$ ll /dev/snd/
/dev/snd/
├── controlC0 # 控制设备(声卡0)
├── pcmC0D0p # PCM 播放设备(卡0,设备0,播放)
├── pcmC0D0c # PCM 录音设备(卡0,设备0,录音)
├── timer # 定时器
└── seq # 音序器(MIDI相关)

controlC0: 用于声卡控制的设备节点,譬如通道选择、混音器、麦克风的控制等, C0 表示声卡 0(card0);pcmC0D0c: 用于录音的 PCM 设备节点。其中 C0 表示 card0,也就是声卡 0;而 D0 表示 device0,也就是设备 0;最后一个字母 c 是 capture 的缩写,表示录音;所以 pcmC0D0c 便是系统的声卡0 中的录音设备 0;pcmC0D0p: 用于播放(或叫放音、回放)的 PCM 设备节点。其中 C0 表示 card0,也就是声卡 0;而 D0 表示 device 0,也就是设备 0;最后一个字母 p 是 playback 的缩写,表示播放;所以 pcmC0D0p便是系统的声卡 0 中的播放设备 0;pcmC0D1c: 用于录音的 PCM 设备节点。对应系统的声卡 0 中的录音设备 1;pcmC0D1p: 用于播放的 PCM 设备节点。对应系统的声卡 0 中的播放设备 1。timer: 定时器。
2. PCM 设备节点命名规则(pcmC[卡号]D[设备号][p/c])
pcmC[卡号]D[设备号][p/c]
pcmCxDyp → PCM, Card x, Device y, Playback (播放)
pcmCxDyc → PCM, Card x, Device y, Capture (录音)
例如:
pcmC0D0p → 声卡0,设备0,播放(Playback)
pcmC0D0c → 声卡0,设备0,录音(Capture)
pcmC0D1p → 声卡0,设备1,播放(Playback)
pcmC0D1c → 声卡0,设备1,录音(Capture)
pcmC1D2p → 声卡1,设备2,播放
(二) 【核心】ALSA 框架驱动中的声卡、设备、PCM 设备
1. Card(声卡,音频编解码器):/proc/asound/cardX/
- ALSA 把一块完整的音频子系统抽象为一张“卡”。
- 这张“卡”可能是主板上的集成声卡、USB 声卡、I²S 接口挂接的编解码器,甚至是虚拟声卡(如 loopback)。

root@ATK-IMX6U:~/mnt/out# ll /proc/asound/card0/
total 0
-r--r--r-- 1 root root 0 Dec 7 18:36 id
dr-xr-xr-x 3 root root 0 Dec 7 18:36 pcm0c/
dr-xr-xr-x 3 root root 0 Dec 7 18:36 pcm0p/
dr-xr-xr-x 3 root root 0 Dec 7 18:36 pcm1c/
dr-xr-xr-x 3 root root 0 Dec 7 18:36 pcm1p/

card0 目录下记录了声卡 0 相关的信息,譬如声卡的名字以及声卡注册的 PCM 设备
2. Device(设备号,功能单元/所有声卡注册的设备):/proc/asound/devices

3. PCM Device(所有 PCM 设备):/proc/asound/pcm

六、【调试方式】命令行、alsa-utils 工具
(一) ALSA 框架驱动部分
ll /dev/snd/
cards:
通过"cat /proc/asound/cards"命令、查看 cards 文件的内容,可列出系统中可用的、注册的声卡,如下所示:
cat /proc/asound/cards
devices:
列出系统中所有声卡注册的设备,包括 control、 pcm、 timer、 seq 等等。如下所示:
cat /proc/asound/devices
pcm:
列出系统中的所有 PCM 设备,包括 playback 和 capture:
cat /proc/asound/pcm
(二) alsa-utils 工具(查看音频设备信息)
# 查看所有声卡
aplay -l
# 查看所有 PCM 设备
aplay -L
# 查看录音设备
arecord -l
# 查看混音器控制
amixer
# 播放测试音频
aplay test.wav
# 录音测试
arecord -d 10 -f cd -t wav test.wav
七、【开发基础】ALSA & PCM 基础知识
(一) 核心概念图解
┌────────────────────────────────────────────────────────────┐
│ 音频数据的层次结构 │
│ │
│ Sample (样本) │
│ ↓ │
│ Frame (帧) = 所有声道的样本 │
│ ↓ │
│ Period (周期) = 多个帧 │
│ ↓ │
│ Buffer (缓冲区) = 多个周期 │
└────────────────────────────────────────────────────────────┘
(二) 样本长度(Sample / Bit Depth)
1. 什么是样本?
**样本(Sample)**是记录音频数据最基本的单元。
模拟音频信号: ~~~~~~~~~ (连续的波形)
↓ ADC采样
数字音频信号: ■ ■ ■ ■ ■ ■ ■ ■ (离散的采样点)
↑
每个点就是一个"样本"
2. 样本长度(位深度)
样本长度 = 每个样本用多少位(bit)来表示
常用位深度:
├─ 8 bit → 256 个级别 (电话质量)
├─ 16 bit → 65536 个级别 (CD 音质) ← 最常用
├─ 24 bit → 1677万个级别 (高保真)
└─ 32 bit → 42亿个级别 (专业音频)
位深度越高 → 声音越细腻 → 文件越大
3. 数值示例
// 8位无符号: 0-255
unsigned char sample_8bit = 128; // 静音值
// 16位有符号: -32768 到 32767
short sample_16bit = 0; // 静音值
// 32位浮点: -1.0 到 1.0
float sample_32bit = 0.0f; // 静音值
(三) 声道数(Channel)
1. 什么是声道?
**声道(Channel)**是独立的音频通道。
单声道(Mono):
[L] [L] [L] [L] ...
↓ ↓ ↓ ↓
只有一个音频通道
双声道/立体声(Stereo):
[L] [R] [L] [R] ...
↓ ↓ ↓ ↓
左声道 右声道
多声道(Surround):
5.1声道: 左前 + 右前 + 中置 + 左后 + 右后 + 低音炮
7.1声道: 更多声道
2. 常用声道配置
1 = 单声道(Mono) - 电话、对讲机
2 = 立体声(Stereo) - 音乐、电影(最常用)
4 = 四声道 - 环绕声
6 = 5.1 声道 - 家庭影院
8 = 7.1 声道 - 专业影院
(四) 帧(Frame)
1. 什么是帧?
帧(Frame)记录了一个声音单元,长度为样本长度 × 声道数。
单声道,16位:
┌────────┐
│ 左声道 │ ← 1帧 = 2字节
│ 2字节 │
└────────┘
双声道,16位:
┌────────┬────────┐
│ 左声道 │ 右声道 │ ← 1帧 = 4字节
│ 2字节 │ 2字节 │
└────────┴────────┘
2. 帧大小计算
帧大小(字节)= 样本长度(bit)/ 8 × 声道数
示例:
- 16位单声道: 16/8 × 1 = 2 字节/帧
- 16位双声道: 16/8 × 2 = 4 字节/帧
- 24位双声道: 24/8 × 2 = 6 字节/帧
- 32位双声道: 32/8 × 2 = 8 字节/帧
3. 在 ALSA 中
// 帧是 ALSA API 的基本单位
snd_pcm_writei(pcm, buffer, 1024); // 写入 1024 帧
snd_pcm_readi(pcm, buffer, 1024); // 读取 1024 帧
// 注意:不是字节数,是帧数!
(五) 采样率(Sample Rate)
1. 什么是采样率?
采样率 = 每秒钟采样次数(Hz)
44.1 kHz = 每秒采样 44100 次
时间轴: |----1秒----|
采样: ●●●●●●●... (44100个采样点)
采样率越高 → 声音越真实 → 文件越大
2. 常用采样率对照表
采样率 | 用途 | 音质 |
8 kHz | 电话通信 | 低 |
16 kHz | 语音识别、对讲 | 中低 |
22.05 kHz | FM 广播 | 中 |
44.1 kHz | CD 音质 | 高 ⭐ 最常用 |
48 kHz | DVD、专业音频 | 高 |
96 kHz | 高清音频 | 超高 |
192 kHz | 发烧级音频 | 极高 |
3. 计算示例
数据速率 = 采样率 × 帧大小
示例:44.1kHz, 16位, 双声道
= 44100 samples/秒 × 4 字节/帧
= 176400 字节/秒
= 172.3 KB/秒
(六) 交错模式(Interleaved)
1. 两种数据存储方式
一、交错模式(Interleaved)⭐ 常用
左右声道数据交替存储
内存布局:
┌──┬──┬──┬──┬──┬──┬──┬──┐
│L0│R0│L1│R1│L2│R2│L3│R3│...
└──┴──┴──┴──┴──┴──┴──┴──┘
优点:
✅ 数据连续,方便处理
✅ ALSA 默认使用
✅ 硬件 DMA 传输友好
二、非交错模式(Non-interleaved)
左右声道分开存储
内存布局:
左声道缓冲区:
┌──┬──┬──┬──┬──┬──┐
│L0│L1│L2│L3│L4│L5│...
└──┴──┴──┴──┴──┴──┘
右声道缓冲区:
┌──┬──┬──┬──┬──┬──┐
│R0│R1│R2│R3│R4│R5│...
└──┴──┴──┴──┴──┴──┘
优点:
✅ 方便单独处理某个声道
❌ 使用较少
2. ALSA 代码示例
/* 交错模式(常用) */
snd_pcm_hw_params_set_access(pcm, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
/* 非交错模式 */
snd_pcm_hw_params_set_access(pcm, params,
SND_PCM_ACCESS_RW_NONINTERLEAVED);
(七) 周期(Period)
1. 什么是周期?
周期(Period)= 音频设备每次处理(读/写)数据的单位,单位是帧
周期是硬件中断的触发单元
周期大小 = 1024 帧(常用值)
2. 周期的作用
播放流程:
应用程序 ──写入 1024 帧──→ 缓冲区
↓
硬件读取 1024 帧
↓
触发中断(1个周期处理完)
↓
通知应用:可以写下一个周期
3. 周期大小的影响
周期越大:
✅ CPU 中断次数少,效率高
❌ 延迟大
周期越小:
✅ 延迟小,实时性好
❌ CPU 中断频繁,效率低
典型值:
- 低延迟应用:256 或 512 帧
- 普通应用:1024 帧 ⭐
- 大缓冲:2048 或 4096 帧
4. 计算示例
周期 = 1024 帧
采样率 = 44100 Hz
帧大小 = 4 字节(16位双声道)
周期时间 = 1024 / 44100 ≈ 23.2 毫秒
周期字节数 = 1024 × 4 = 4096 字节 = 4 KB
(八) 缓冲区(Buffer)
1. 什么是缓冲区?
缓冲区(Buffer)= 驱动层的环形缓冲区,由多个周期组成
Buffer = Period × Period数量
示例:
Period Size = 1024 帧
Periods = 16
Buffer Size = 1024 × 16 = 16384 帧
2. 环形缓冲区可视化
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ P0 │ P1 │ P2 │ P3 │ P4 │ P5 │ P6 │ P7 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑
write read
pointer pointer
P0-P7 = Period 0-7 (假设 8 个周期)
指针到达末尾后,会回到起始位置(环形)
3. Buffer/Period/Frame/Sample 关系图
┌──────────────── Buffer (缓冲区) ────────────────┐
│ │
│ ┌─────── Period 0 ──────┬─────── Period 1 ────┤
│ │ │ │
│ │ ┌────┬────┬────┬─── │ ┌────┬────┬────┬── │
│ │ │ F0 │ F1 │ F2 │... │ │1024│1025│1026│.. │
│ │ └────┴────┴────┴─── │ └────┴────┴────┴── │
│ │ ↑ │ ↑ │
│ │ 帧(Frame) │ 帧 │
│ └───────────────────────┴─────────────────────┤
│ │
│ 每个 Period = 1024 帧 │
│ 假设 16 个 Period │
│ 总 Buffer = 16384 帧 │
└─────────────────────────────────────────────────┘
每帧结构(16位双声道):
┌──────────┬──────────┐
│ 左声道样本│ 右声道样本│ ← 1 帧 = 4 字节
│ 2 字节 │ 2 字节 │
└──────────┴──────────┘
↑ ↑
Sample 0 Sample 1
(九) 【核心】数据传输流程(PCM 播放/录音)
1. 播放(Playback)
初始状态:Buffer 为空
应用程序写入 → Buffer
│
↓
┌────────────────────────────────┐
│ ■■■■ ════════════════════════ │
│ ↑写指针 ↑读指针 │
└────────────────────────────────┘
已填充 空闲
硬件不断读取 → 播放声音
│
↓
┌────────────────────────────────┐
│ ════ ■■■■ ════════════════════ │
│ ↑写指针 ↑读指针 │
└────────────────────────────────┘
指针移动,环形循环:
写指针 → → → → → 末尾 → 回到开头 → → →
读指针 → → → → → 末尾 → 回到开头 → → →
2. 录音(Capture)
初始状态:Buffer 为空
硬件不断写入 → Buffer
│
↓
┌────────────────────────────────┐
│ ■■■■ ════════════════════════ │
│ ↑读指针 ↑写指针 │
└────────────────────────────────┘
已录制 空闲
应用程序读取 → 保存到文件
│
↓
┌────────────────────────────────┐
│ ════ ■■■■ ════════════════════ │
│ ↑读指针 ↑写指针 │
└────────────────────────────────┘
3. 完整数据流
播放流程:
应用程序 内核驱动 硬件
│ │ │
│ write(音频数据) │ │
├─────────────────────→│ │
│ │ 放入 Buffer │
│ │ 触发 DMA │
│ ├───────────────────→│
│ │ │ DMA 传输
│ │ │ I2S 输出
│ │ │ → CODEC
│ │ │ →
│ │ │
│ │ 一个周期播放完 │
│ │←───中断────────────│
│ 可以写下一个周期 │ │
│←─────────────────────│ │
│ │ │
录音流程正好相反:
→ CODEC → I2S → DMA → Buffer → 应用程序读取
(十) Over Run 和 Under Run(XRUN)
1. Under Run(下溢)- 播放时
原因: 应用程序写入数据太慢,Buffer 被硬件读空了
正常情况:
Buffer: [■■■■■■══════════]
↑读 ↑写
硬件 应用
Under Run(饿死):
Buffer: [════════════════════]
↑读=写
Buffer 空了!无数据可播放!
结果:
- 播放出现"咔咔"噪音
- ALSA 状态变为 SND_PCM_STATE_XRUN
2. Over Run(上溢)- 录音时
原因: 应用程序读取数据太慢,Buffer 被硬件写满了
正常情况:
Buffer: [■■■■══════■■■■■■]
↑读 ↑写
应用 硬件
Over Run(撑满):
Buffer: [■■■■■■■■■■■■■■■■]
↑读=写
Buffer 满了!无空间可写!
结果:
- 录音数据丢失
- ALSA 状态变为 SND_PCM_STATE_XRUN
3. XRUN 恢复
/**
* @brief 从 XRUN 恢复
*/
if (snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
fprintf(stderr, "发生 XRUN,正在恢复...\n");
// 重新准备设备
snd_pcm_prepare(pcm);
// 继续播放/录音
}
// 或者在写入时自动恢复
ret = snd_pcm_writei(pcm, buffer, frames);
if (ret == -EPIPE) { // -EPIPE 表示 XRUN
snd_pcm_prepare(pcm);
snd_pcm_writei(pcm, buffer, frames); // 重试
}
(十一) 一图看懂所有概念
音频数据的完整结构:
时间轴: |─────────── 1 秒 (44100 个采样点) ────────────|
采样点: ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ... (44100个)
↑
Sample
(样本)
双声道: [L] [R] [L] [R] [L] [R] [L] [R] ...
└────┘
Frame
(1帧 = 4字节)
分组: |─ Period ─|─ Period ─|─ Period ─| ...
| 1024 帧 | 1024 帧 | 1024 帧 |
| 4KB | 4KB | 4KB |
缓冲区: |════════ Buffer = 16 Periods ════════|
| 16384 帧 = 64 KB |
数据流: 应用→Buffer→DMA→I2S→CODEC→
存储: 交错模式 [L0 R0 L1 R1 L2 R2 ...]
└──────┘
连续存储
八、【相关 API】alsa-lib 库 API 介绍
(一) PCM 设备管理
1. snd_pcm_open - 打开一个 PCM 音频设备。
参数 | 类型 | 说明 |
pcmp | snd_pcm_t ** | 返回的 PCM 句柄指针 |
name | const char * | PCM 设备名称(如 "hw:0,0", "default", "plughw:0,0")
|
stream | snd_pcm_stream_t | 流类型:
|
mode | int | 打开模式:
|
返回值:成功返回 0,失败返回负数错误码
示例:
snd_pcm_t *pcm;
snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
2. snd_pcm_close - 关闭 PCM 设备,释放相关资源。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
3. snd_pcm_drain - 等待所有待处理的音频帧播放完毕。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
4. snd_pcm_drop - 立即停止 PCM 设备,丢弃所有待处理的音频帧。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
5. snd_pcm_prepare - 准备 PCM 设备用于数据传输,将 PCM 状态设置为 PREPARED。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
6. snd_pcm_reset - 立即停止 PCM,将 PCM 状态重置为 PREPARED。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
7. snd_pcm_pause - 暂停或恢复 PCM 设备。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
enable | int | 1 表示暂停,0 表示恢复 |
返回值:成功返回 0,失败返回负数错误码
8. snd_pcm_resume - 从挂起状态恢复 PCM 设备。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回 0,失败返回负数错误码
(二) PCM 硬件参数配置
1. snd_pcm_hw_params_malloc - 为硬件参数结构分配内存。
参数 | 类型 | 说明 |
ptr | snd_pcm_hw_params_t ** | 返回的硬件参数对象指针 |
返回值:成功返回 0,失败返回负数错误码
2. snd_pcm_hw_params_free - 释放硬件参数结构的内存。
参数 | 类型 | 说明 |
obj | snd_pcm_hw_params_t * | 硬件参数对象 |
返回值:无
3. snd_pcm_hw_params_any - 初始化硬件参数为设备支持的完整配置空间。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
返回值:成功返回 0,失败返回负数错误码
4. snd_pcm_hw_params - 将配置好的硬件参数应用到 PCM 设备。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
返回值:成功返回 0,失败返回负数错误码
5. snd_pcm_hw_params_set_access - 设置访问类型(交错/非交错)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
access | snd_pcm_access_t | 访问类型:SND_PCM_ACCESS_RW_INTERLEAVED(交错)、SND_PCM_ACCESS_RW_NONINTERLEAVED(非交错)等 |
返回值:成功返回 0,失败返回负数错误码
常用访问类型:
SND_PCM_ACCESS_RW_INTERLEAVED:读写交错模式(常用)SND_PCM_ACCESS_RW_NONINTERLEAVED:读写非交错模式SND_PCM_ACCESS_MMAP_INTERLEAVED:内存映射交错模式SND_PCM_ACCESS_MMAP_NONINTERLEAVED:内存映射非交错模式
6. snd_pcm_hw_params_set_format - 设置音频数据格式。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
format | snd_pcm_format_t | 音频格式(如 SND_PCM_FORMAT_S16_LE) |
返回值:成功返回 0,失败返回负数错误码
常用格式:
SND_PCM_FORMAT_S8:8位有符号SND_PCM_FORMAT_U8:8位无符号SND_PCM_FORMAT_S16_LE:16位有符号小端(常用)SND_PCM_FORMAT_S16_BE:16位有符号大端SND_PCM_FORMAT_S24_LE:24位有符号小端SND_PCM_FORMAT_S32_LE:32位有符号小端SND_PCM_FORMAT_FLOAT_LE:32位浮点小端
7. snd_pcm_hw_params_set_rate - 设置采样率。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | unsigned int | 采样率(Hz),如 8000, 16000, 44100, 48000 |
dir | int | 方向标志,通常为 0 |
返回值:成功返回 0,失败返回负数错误码
8. snd_pcm_hw_params_set_rate_near - 设置最接近的采样率。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | unsigned int * | 输入期望采样率,输出实际采样率 |
dir | int * | 方向标志指针 |
返回值:成功返回 0,失败返回负数错误码
9. snd_pcm_hw_params_set_channels - 设置声道数。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | unsigned int | 声道数(1=单声道,2=立体声) |
返回值:成功返回 0,失败返回负数错误码
10. snd_pcm_hw_params_set_period_size - 设置周期大小(每个中断的帧数)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | snd_pcm_uframes_t | 周期大小(帧数) |
dir | int | 方向标志,通常为 0 |
返回值:成功返回 0,失败返回负数错误码
11. snd_pcm_hw_params_set_period_size_near - 设置最接近的周期大小。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | snd_pcm_uframes_t * | 输入期望周期大小,输出实际周期大小 |
dir | int * | 方向标志指针 |
返回值:成功返回 0,失败返回负数错误码
12. snd_pcm_hw_params_set_periods - 设置周期数(buffer 中包含的周期数)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | unsigned int | 周期数 |
dir | int | 方向标志,通常为 0 |
返回值:成功返回 0,失败返回负数错误码
13. snd_pcm_hw_params_set_buffer_size - 设置 buffer 大小(总帧数)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_hw_params_t * | 硬件参数对象 |
val | snd_pcm_uframes_t | buffer 大小(帧数) |
返回值:成功返回 0,失败返回负数错误码
14. snd_pcm_hw_params_get_period_size - 获取周期大小。
参数 | 类型 | 说明 |
params | const snd_pcm_hw_params_t * | 硬件参数对象 |
frames | snd_pcm_uframes_t * | 返回的周期大小 |
dir | int * | 方向标志指针 |
返回值:成功返回 0,失败返回负数错误码
15. snd_pcm_hw_params_get_buffer_size - 获取 buffer 大小。
参数 | 类型 | 说明 |
params | const snd_pcm_hw_params_t * | 硬件参数对象 |
val | snd_pcm_uframes_t * | 返回的 buffer 大小 |
返回值:成功返回 0,失败返回负数错误码
(三) PCM 软件参数配置
1. snd_pcm_sw_params_malloc - 为软件参数结构分配内存。
参数 | 类型 | 说明 |
ptr | snd_pcm_sw_params_t ** | 返回的软件参数对象指针 |
返回值:成功返回 0,失败返回负数错误码
2. snd_pcm_sw_params_free - 释放软件参数结构的内存。
参数 | 类型 | 说明 |
obj | snd_pcm_sw_params_t * | 软件参数对象 |
返回值:无
3. snd_pcm_sw_params_current - 获取当前软件参数配置。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_sw_params_t * | 软件参数对象 |
返回值:成功返回 0,失败返回负数错误码
4. snd_pcm_sw_params - 将软件参数应用到 PCM 设备。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_sw_params_t * | 软件参数对象 |
返回值:成功返回 0,失败返回负数错误码
5. snd_pcm_sw_params_set_start_threshold - 设置自动启动的阈值。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_sw_params_t * | 软件参数对象 |
val | snd_pcm_uframes_t | 启动阈值(帧数) |
返回值:成功返回 0,失败返回负数错误码
6. snd_pcm_sw_params_set_stop_threshold - 设置自动停止的阈值。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_sw_params_t * | 软件参数对象 |
val | snd_pcm_uframes_t | 停止阈值(帧数) |
返回值:成功返回 0,失败返回负数错误码
7. snd_pcm_sw_params_set_avail_min - 设置可用空间的最小值,用于唤醒等待的应用程序。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
params | snd_pcm_sw_params_t * | 软件参数对象 |
val | snd_pcm_uframes_t | 最小可用帧数 |
返回值:成功返回 0,失败返回负数错误码
(四) PCM 数据传输
1. snd_pcm_writei - 向 PCM 设备写入交错模式的音频数据(播放)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
buffer | const void * | 音频数据缓冲区 |
size | snd_pcm_uframes_t | 要写入的帧数 |
返回值:成功返回实际写入的帧数,失败返回负数错误码
2. snd_pcm_writen - 向 PCM 设备写入非交错模式的音频数据。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
bufs | void ** | 音频数据缓冲区数组(每个声道一个缓冲区) |
size | snd_pcm_uframes_t | 要写入的帧数 |
返回值:成功返回实际写入的帧数,失败返回负数错误码
3. snd_pcm_readi - 从 PCM 设备读取交错模式的音频数据(录音)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
buffer | void * | 音频数据缓冲区 |
size | snd_pcm_uframes_t | 要读取的帧数 |
返回值:成功返回实际读取的帧数,失败返回负数错误码
4. snd_pcm_readn - 从 PCM 设备读取非交错模式的音频数据。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
bufs | void ** | 音频数据缓冲区数组 |
size | snd_pcm_uframes_t | 要读取的帧数 |
返回值:成功返回实际读取的帧数,失败返回负数错误码
5. snd_pcm_wait - 等待 PCM 设备变为可读或可写状态。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
timeout | int | 超时时间(毫秒),-1 表示无限等待 |
返回值:成功返回正数,超时返回 0,失败返回负数错误码
(五) PCM 状态查询
1. snd_pcm_state - 获取 PCM 设备的当前状态。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:返回 snd_pcm_state_t 枚举值
PCM 状态:
SND_PCM_STATE_OPEN:已打开SND_PCM_STATE_SETUP:已设置SND_PCM_STATE_PREPARED:已准备SND_PCM_STATE_RUNNING:运行中SND_PCM_STATE_XRUN:发生 xrun(欠载/溢出)SND_PCM_STATE_DRAINING:排空中SND_PCM_STATE_PAUSED:已暂停SND_PCM_STATE_SUSPENDED:已挂起SND_PCM_STATE_DISCONNECTED:已断开
2. snd_pcm_avail - 获取当前可用的帧数。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回可用帧数,失败返回负数错误码
3. snd_pcm_avail_update - 更新并获取当前可用的帧数。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
返回值:成功返回可用帧数,失败返回负数错误码
4. snd_pcm_delay - 获取硬件延迟(尚未播放/录制的帧数)。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
delayp | snd_pcm_sframes_t * | 返回的延迟帧数 |
返回值:成功返回 0,失败返回负数错误码
(六) PCM 异步处理
1. snd_async_add_pcm_handler - 为 PCM 设备添加异步回调处理器。
参数 | 类型 | 说明 |
handler | snd_async_handler_t ** | 返回的异步处理器指针 |
pcm | snd_pcm_t * | PCM 句柄 |
callback | snd_async_callback_t | 回调函数 |
private_data | void * | 传递给回调函数的私有数据 |
返回值:成功返回 0,失败返回负数错误码
2. snd_async_handler_get_pcm - 从异步处理器获取 PCM 句柄。
参数 | 类型 | 说明 |
handler | snd_async_handler_t * | 异步处理器 |
返回值:返回 PCM 句柄
(七) Mixer 控制(音量控制)
1. snd_mixer_open - 打开一个 Mixer 设备。
参数 | 类型 | 说明 |
mixerp | snd_mixer_t ** | 返回的 Mixer 句柄指针 |
mode | int | 打开模式,通常为 0 |
返回值:成功返回 0,失败返回负数错误码
2. snd_mixer_close - 关闭 Mixer 设备。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
返回值:成功返回 0,失败返回负数错误码
3. snd_mixer_attach - 将 Mixer 附加到声卡。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
name | const char * | 声卡名称(如 "hw:0") |
返回值:成功返回 0,失败返回负数错误码
4. snd_mixer_detach - 从声卡分离 Mixer。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
name | const char * | 声卡名称 |
返回值:成功返回 0,失败返回负数错误码
5. snd_mixer_load - 加载 Mixer 元素。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
返回值:成功返回 0,失败返回负数错误码
6. snd_mixer_selem_register - 注册简单元素类。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
options | struct snd_mixer_selem_regopt * | 注册选项,通常为 NULL |
classp | snd_mixer_class_t ** | 返回的类指针 |
返回值:成功返回 0,失败返回负数错误码
7. snd_mixer_first_elem - 获取第一个 Mixer 元素。
参数 | 类型 | 说明 |
mixer | snd_mixer_t * | Mixer 句柄 |
返回值:返回 snd_mixer_elem_t * 指针
8. snd_mixer_elem_next - 获取下一个 Mixer 元素。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | 当前元素 |
返回值:返回下一个元素指针,无下一个则返回 NULL
9. snd_mixer_selem_get_name - 获取简单元素的名称。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
返回值:返回名称字符串
10. snd_mixer_selem_has_playback_volume - 检查元素是否有播放音量控制。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
返回值:有返回 1,无返回 0
11. snd_mixer_selem_get_playback_volume - 获取播放音量值。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
channel | snd_mixer_selem_channel_id_t | 声道 ID |
value | long * | 返回的音量值 |
返回值:成功返回 0,失败返回负数错误码
12. snd_mixer_selem_set_playback_volume - 设置播放音量值。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
channel | snd_mixer_selem_channel_id_t | 声道 ID |
value | long | 音量值 |
返回值:成功返回 0,失败返回负数错误码
13. snd_mixer_selem_set_playback_volume_all - 设置所有声道的播放音量。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
value | long | 音量值 |
返回值:成功返回 0,失败返回负数错误码
14. snd_mixer_selem_get_playback_volume_range - 获取播放音量范围。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
min | long * | 返回的最小值 |
max | long * | 返回的最大值 |
返回值:成功返回 0,失败返回负数错误码
15. snd_mixer_selem_set_playback_switch - 设置播放开关(静音/非静音)。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
channel | snd_mixer_selem_channel_id_t | 声道 ID |
value | int | 1 为开启,0 为关闭(静音) |
返回值:成功返回 0,失败返回负数错误码
16. snd_mixer_selem_set_playback_switch_all - 设置所有声道的播放开关。
参数 | 类型 | 说明 |
elem | snd_mixer_elem_t * | Mixer 元素 |
value | int | 1 为开启,0 为关闭(静音) |
返回值:成功返回 0,失败返回负数错误码
(八) 错误处理与工具函数
1. snd_strerror - 将错误码转换为可读的错误描述字符串。
参数 | 类型 | 说明 |
errnum | int | 错误码(负数) |
返回值:返回错误描述字符串
示例:
int err = snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
fprintf(stderr, "Error: %s\n", snd_strerror(err));
}
2. snd_pcm_format_size - 获取指定格式的样本大小(字节数)。
参数 | 类型 | 说明 |
format | snd_pcm_format_t | 音频格式 |
samples | size_t | 样本数 |
返回值:返回字节数,错误返回负数
3. snd_pcm_format_physical_width - 获取指定格式的物理位宽。
参数 | 类型 | 说明 |
format | snd_pcm_format_t | 音频格式 |
返回值:返回位宽(位数)
4. snd_pcm_format_width - 获取指定格式的有效位宽。
参数 | 类型 | 说明 |
format | snd_pcm_format_t | 音频格式 |
返回值:返回位宽(位数)
5. snd_pcm_bytes_to_frames - 将字节数转换为帧数。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
bytes | ssize_t | 字节数 |
返回值:返回帧数
6. snd_pcm_frames_to_bytes - 将帧数转换为字节数。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
frames | snd_pcm_sframes_t | 帧数 |
返回值:返回字节数
(九) Control 接口(高级控制)
1. snd_ctl_open - 打开控制接口。
参数 | 类型 | 说明 |
ctlp | snd_ctl_t ** | 返回的控制句柄指针 |
name | const char * | 声卡名称(如 "hw:0") |
mode | int | 打开模式,通常为 0 |
返回值:成功返回 0,失败返回负数错误码
2. snd_ctl_close - 关闭控制接口。
参数 | 类型 | 说明 |
ctl | snd_ctl_t * | 控制句柄 |
返回值:成功返回 0,失败返回负数错误码
3. snd_ctl_card_info - 获取声卡信息。
参数 | 类型 | 说明 |
ctl | snd_ctl_t * | 控制句柄 |
info | snd_ctl_card_info_t * | 声卡信息结构 |
返回值:成功返回 0,失败返回负数错误码
4. snd_card_next - 获取下一个声卡索引。
参数 | 类型 | 说明 |
card | int * | 输入当前卡号,输出下一个卡号(-1 表示无更多声卡) |
返回值:成功返回 0,失败返回负数错误码
5. snd_card_get_name - 获取声卡名称。
参数 | 类型 | 说明 |
card | int | 声卡索引 |
name | char ** | 返回的名称字符串指针 |
返回值:成功返回 0,失败返回负数错误码
6. snd_card_get_longname - 获取声卡完整名称。
参数 | 类型 | 说明 |
card | int | 声卡索引 |
name | char ** | 返回的名称字符串指针 |
返回值:成功返回 0,失败返回负数错误码
(十) XRUN 处理(欠载/溢出)
1. snd_pcm_recover - 从错误状态恢复 PCM 设备。
参数 | 类型 | 说明 |
pcm | snd_pcm_t * | PCM 句柄 |
err | int | 错误码 |
silent | int | 是否静默处理(1 为静默,0 为输出消息) |
返回值:成功返回 0,失败返回负数错误码
说明:该函数可处理 -EPIPE(xrun)和 -ESTRPIPE(挂起)错误。
(十一) 常用数据类型
1. snd_pcm_uframes_t
无符号帧数类型(通常为 unsigned long)
2. snd_pcm_sframes_t
有符号帧数类型(通常为 long)
3. snd_pcm_t
PCM 设备句柄
4. snd_pcm_hw_params_t
硬件参数结构
5. snd_pcm_sw_params_t
软件参数结构
6. snd_mixer_t
Mixer 句柄
7. snd_mixer_elem_t
Mixer 元素
8. snd_ctl_t
控制接口句柄
(十二) 典型使用流程
1. 播放音频流程
// 1. 打开 PCM 设备
snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
// 2. 配置硬件参数
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm, hwparams);
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm, hwparams, 44100, 0);
snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
snd_pcm_hw_params(pcm, hwparams);
snd_pcm_hw_params_free(hwparams);
// 3. 写入音频数据
snd_pcm_writei(pcm, buffer, frames);
// 4. 关闭设备
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
2. 录音音频流程
// 1. 打开 PCM 设备
snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_CAPTURE, 0);
// 2. 配置硬件参数(同播放)
// ...
// 3. 读取音频数据
snd_pcm_readi(pcm, buffer, frames);
// 4. 关闭设备
snd_pcm_close(pcm);
3. Mixer 音量控制流程
// 1. 打开 Mixer
snd_mixer_open(&mixer, 0);
snd_mixer_attach(mixer, "hw:0");
snd_mixer_selem_register(mixer, NULL, NULL);
snd_mixer_load(mixer);
// 2. 查找音量控制元素
snd_mixer_selem_id_t *sid;
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_name(sid, "Master");
snd_mixer_elem_t *elem = snd_mixer_find_selem(mixer, sid);
// 3. 设置音量
long min, max;
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_set_playback_volume_all(elem, (max - min) / 2);
// 4. 关闭 Mixer
snd_mixer_close(mixer);
九、【开发记录】PCM 播放详解(播放 WAV 文件)
(一) PCM 播放流程
┌─────────────────────────────────────────────────┐
│ 步骤1: 打开 PCM 设备 │
│ snd_pcm_open() │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤2: 设置硬件参数 │
│ - 创建参数对象: snd_pcm_hw_params_malloc() │
│ - 初始化: snd_pcm_hw_params_any() │
│ - 设置访问模式: set_access() │
│ - 设置格式: set_format() │
│ - 设置采样率: set_rate() │
│ - 设置声道数: set_channels() │
│ - 设置周期大小: set_period_size() │
│ - 设置周期数: set_periods() │
│ - 使生效: snd_pcm_hw_params() │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤3: 写入音频数据 │
│ 循环:snd_pcm_writei() │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤4: 关闭设备 │
│ snd_pcm_close() │
└─────────────────────────────────────────────────┘
(二) 关键 API 函数
1. 打开 PCM 设备
/**
* @brief 打开 PCM 设备
* @param pcm PCM 句柄指针
* @param name 设备名称(如 "hw:0,0" 或 "default")
* @param stream 数据流方向
* - SND_PCM_STREAM_PLAYBACK: 播放
* - SND_PCM_STREAM_CAPTURE: 录音
* @param mode 打开模式(通常为 0,阻塞模式)
* @return 0=成功,负数=失败
*/
int snd_pcm_open(snd_pcm_t **pcm,
const char *name,
snd_pcm_stream_t stream,
int mode);
// 使用示例
snd_pcm_t *pcm;
int ret;
ret = snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0) {
fprintf(stderr, "无法打开设备: %s\n", snd_strerror(ret));
return -1;
}
设备名称说明:
"hw:0,0" → 声卡0,设备0(直接访问硬件)
"hw:1,2" → 声卡1,设备2
"default" → 默认设备(推荐,会经过 ALSA 插件处理)
"plughw:0,0" → 通过插件访问(自动进行采样率转换等)
2. 设置硬件参数
snd_pcm_hw_params_t *hwparams;
snd_pcm_t *pcm;
/* 分配参数对象 */
snd_pcm_hw_params_malloc(&hwparams);
/* 初始化参数对象(获取当前硬件配置) */
snd_pcm_hw_params_any(pcm, hwparams);
/* 设置访问类型:交错模式 */
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
/* 设置数据格式:有符号16位小端 */
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
/* 设置采样率:44100 Hz */
unsigned int rate = 44100;
snd_pcm_hw_params_set_rate(pcm, hwparams, rate, 0);
/* 设置声道数:2(立体声) */
snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
/* 设置周期大小:1024 帧 */
snd_pcm_uframes_t period_size = 1024;
snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
/* 设置周期数:16 */
unsigned int periods = 16;
snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
/* 使配置生效 */
snd_pcm_hw_params(pcm, hwparams);
/* 释放参数对象 */
snd_pcm_hw_params_free(hwparams);
常用音频格式:
SND_PCM_FORMAT_S16_LE // 有符号16位小端(最常用)
SND_PCM_FORMAT_S24_LE // 有符号24位小端
SND_PCM_FORMAT_S32_LE // 有符号32位小端
SND_PCM_FORMAT_U8 // 无符号8位
SND_PCM_FORMAT_FLOAT_LE // 浮点格式
访问模式:
SND_PCM_ACCESS_RW_INTERLEAVED // 交错模式(常用)
SND_PCM_ACCESS_RW_NONINTERLEAVED // 非交错模式
交错模式 vs 非交错模式:
交错模式(Interleaved):
[L0 R0 L1 R1 L2 R2 ...]
左右声道交替存储
非交错模式(Non-interleaved):
[L0 L1 L2 L3 ...] [R0 R1 R2 R3 ...]
左右声道分开存储
3. 写入音频数据
/**
* @brief 写入音频帧(交错模式)
* @param pcm PCM 句柄
* @param buffer 数据缓冲区
* @param size 要写入的帧数
* @return 实际写入的帧数,负数表示错误
*/
snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm,
const void *buffer,
snd_pcm_uframes_t size);
// 使用示例
char buffer[8192]; // 缓冲区
snd_pcm_uframes_t frames = 1024; // 要写入的帧数
snd_pcm_sframes_t ret;
ret = snd_pcm_writei(pcm, buffer, frames);
if (ret < 0) {
fprintf(stderr, "写入失败: %s\n", snd_strerror(ret));
// 错误恢复(见后文)
}
else if (ret != frames) {
printf("警告: 只写入了 %ld 帧,期望 %lu 帧\n", ret, frames);
}
4. 关闭设备
/**
* @brief 关闭 PCM 设备
* @param pcm PCM 句柄
*/
int snd_pcm_close(snd_pcm_t *pcm);
// 可选:排空缓冲区后再关闭
snd_pcm_drain(pcm); // 等待缓冲区数据播放完毕
snd_pcm_close(pcm);
(三) 完整播放示例
/***************************************************************
* PCM 播放最简示例
***************************************************************/
#include
#include
#include
int main(void)
{
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hwparams;
unsigned char buffer[8192];
snd_pcm_uframes_t period_size = 1024;
int ret;
/* 1. 打开 PCM 设备 */
ret = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0) {
fprintf(stderr, "打开设备失败: %s\n", snd_strerror(ret));
return -1;
}
/* 2. 设置硬件参数 */
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm, hwparams);
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm, hwparams, 44100, 0);
snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
snd_pcm_hw_params_set_periods(pcm, hwparams, 16, 0);
ret = snd_pcm_hw_params(pcm, hwparams);
if (ret < 0) {
fprintf(stderr, "设置参数失败: %s\n", snd_strerror(ret));
snd_pcm_hw_params_free(hwparams);
snd_pcm_close(pcm);
return -1;
}
snd_pcm_hw_params_free(hwparams);
/* 3. 循环写入数据 */
while (1) {
// 从文件或其他来源读取音频数据到 buffer
// read_audio_data(buffer, period_size * 4);
ret = snd_pcm_writei(pcm, buffer, period_size);
if (ret < 0) {
fprintf(stderr, "写入失败: %s\n", snd_strerror(ret));
break;
}
}
/* 4. 关闭设备 */
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
return 0;
}
十、【开发记录】PCM 录音详解(录制 PCM 数据,得到 PCM 文件)
(一) PCM 录音流程
录音流程与播放类似,只是方向相反:
┌─────────────────────────────────────────────────┐
│ 步骤1: 打开 PCM 设备(录音模式) │
│ snd_pcm_open(..., SND_PCM_STREAM_CAPTURE, 0) │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤2: 设置硬件参数(同播放) │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤3: 读取音频数据 │
│ 循环:snd_pcm_readi() │
└────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 步骤4: 关闭设备 │
│ snd_pcm_close() │
└─────────────────────────────────────────────────┘
(二) 录音 API
/**
* @brief 读取音频帧(交错模式)
* @param pcm PCM 句柄
* @param buffer 数据缓冲区
* @param size 要读取的帧数
* @return 实际读取的帧数,负数表示错误
*/
snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm,
void *buffer,
snd_pcm_uframes_t size);
// 使用示例
char buffer[8192];
snd_pcm_uframes_t frames = 1024;
snd_pcm_sframes_t ret;
ret = snd_pcm_readi(pcm, buffer, frames);
if (ret < 0) {
fprintf(stderr, "读取失败: %s\n", snd_strerror(ret));
}
else {
printf("读取了 %ld 帧数据\n", ret);
// 将数据写入文件或处理
}
(三) 完整录音示例
/***************************************************************
* PCM 录音最简示例
***************************************************************/
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hwparams;
unsigned char buffer[8192];
snd_pcm_uframes_t period_size = 1024;
int fd, ret;
if (argc != 2) {
printf("用法: %s <输出文件>\n", argv[0]);
return -1;
}
/* 打开输出文件 */
fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("打开文件失败");
return -1;
}
/* 1. 打开 PCM 设备(录音) */
ret = snd_pcm_open(&pcm, "hw:0,0", SND_PCM_STREAM_CAPTURE, 0);
if (ret < 0) {
fprintf(stderr, "打开设备失败: %s\n", snd_strerror(ret));
close(fd);
return -1;
}
/* 2. 设置硬件参数 */
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm, hwparams);
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm, hwparams, 44100, 0);
snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
snd_pcm_hw_params_set_periods(pcm, hwparams, 16, 0);
snd_pcm_hw_params(pcm, hwparams);
snd_pcm_hw_params_free(hwparams);
printf("开始录音...(按 Ctrl+C 停止)\n");
/* 3. 循环读取数据 */
while (1) {
ret = snd_pcm_readi(pcm, buffer, period_size);
if (ret < 0) {
fprintf(stderr, "读取失败: %s\n", snd_strerror(ret));
break;
}
/* 写入文件 */
write(fd, buffer, ret * 4); // 16位双声道 = 4字节/帧
}
/* 4. 关闭 */
snd_pcm_close(pcm);
close(fd);
printf("录音完成!\n");
return 0;
}
十一、【开发优化】异步方式编程(PCM 播放/录音)(参考:pcm_*_async.c)
(一) 【前言】数据传输流程(播放/录音):应用程序把音频数据写入内核,内核处理传入硬件(这个过程需要时间)
1. 播放(Playback)
初始状态:Buffer 为空
应用程序写入 → Buffer
│
↓
┌────────────────────────────────┐
│ ■■■■ ════════════════════════ │
│ ↑写指针 ↑读指针 │
└────────────────────────────────┘
已填充 空闲
硬件不断读取 → 播放声音
│
↓
┌────────────────────────────────┐
│ ════ ■■■■ ════════════════════ │
│ ↑写指针 ↑读指针 │
└────────────────────────────────┘
指针移动,环形循环:
写指针 → → → → → 末尾 → 回到开头 → → →
读指针 → → → → → 末尾 → 回到开头 → → →
2. 录音(Capture)
初始状态:Buffer 为空
硬件不断写入 → Buffer
│
↓
┌────────────────────────────────┐
│ ■■■■ ════════════════════════ │
│ ↑读指针 ↑写指针 │
└────────────────────────────────┘
已录制 空闲
应用程序读取 → 保存到文件
│
↓
┌────────────────────────────────┐
│ ════ ■■■■ ════════════════════ │
│ ↑读指针 ↑写指针 │
└────────────────────────────────┘
3. 完整数据流
播放流程:
应用程序 内核驱动 硬件
│ │ │
│ write(音频数据) │ │
├─────────────────────→│ │
│ │ 放入 Buffer │
│ │ 触发 DMA │
│ ├───────────────────→│
│ │ │ DMA 传输
│ │ │ I2S 输出
│ │ │ → CODEC
│ │ │ →
│ │ │
│ │ 一个周期播放完 │
│ │←───中断────────────│
│ 可以写下一个周期 │ │
│←─────────────────────│ │
│ │ │
录音流程正好相反:
→ CODEC → I2S → DMA → Buffer → 应用程序读取
(二) 什么是异步方式?
使用信号机制,当硬件准备好数据时,自动触发回调函数。
传统阻塞方式:
应用程序 → snd_pcm_writei() → 阻塞等待 → 硬件准备好 → 返回
异步方式:
应用程序 → 注册回调函数 → 继续其他工作
↓
硬件准备好 → 触发信号 → 自动调用回调函数
(三) 异步编程步骤
1. 屏蔽 SIGIO 信号
sigset_t sset;
/* 屏蔽 SIGIO 信号(初始化阶段) */
sigemptyset(&sset);
sigaddset(&sset, SIGIO);
sigprocmask(SIG_BLOCK, &sset, NULL);
2. 注册异步回调函数
/**
* @brief 异步回调函数
*/
void playback_callback(snd_async_handler_t *handler)
{
snd_pcm_t *pcm = snd_async_handler_get_pcm(handler);
snd_pcm_sframes_t avail;
/* 获取可用空间 */
avail = snd_pcm_avail_update(pcm);
while (avail >= period_size) {
/* 读取音频数据 */
read(fd, buffer, buf_bytes);
/* 写入 PCM */
snd_pcm_writei(pcm, buffer, period_size);
/* 更新可用空间 */
avail = snd_pcm_avail_update(pcm);
}
}
/* 注册回调 */
snd_async_handler_t *async_handler;
snd_async_add_pcm_handler(&async_handler, pcm, playback_callback, NULL);
3. 取消信号屏蔽
/* 初始填充缓冲区后,取消 SIGIO 屏蔽 */
sigprocmask(SIG_UNBLOCK, &sset, NULL);
(四) 完整异步播放示例
/***************************************************************
* PCM 异步播放示例
***************************************************************/
#include
#include
#include
#include
static snd_pcm_t *pcm;
static unsigned char *buffer;
static snd_pcm_uframes_t period_size = 1024;
static int fd;
/* 异步回调函数 */
void playback_callback(snd_async_handler_t *handler)
{
snd_pcm_t *handle = snd_async_handler_get_pcm(handler);
snd_pcm_sframes_t avail;
int ret;
avail = snd_pcm_avail_update(handle);
while (avail >= period_size) {
ret = read(fd, buffer, period_size * 4);
if (ret <= 0)
return;
snd_pcm_writei(handle, buffer, period_size);
avail = snd_pcm_avail_update(handle);
}
}
int main(int argc, char *argv[])
{
snd_pcm_hw_params_t *hwparams;
snd_async_handler_t *async_handler;
sigset_t sset;
int ret;
/* 屏蔽 SIGIO */
sigemptyset(&sset);
sigaddset(&sset, SIGIO);
sigprocmask(SIG_BLOCK, &sset, NULL);
/* 打开音频文件 */
fd = open(argv[1], O_RDONLY);
/* 打开 PCM 设备并设置参数(省略,同前面) */
// ...
/* 分配缓冲区 */
buffer = malloc(period_size * 4);
/* 注册异步回调 */
snd_async_add_pcm_handler(&async_handler, pcm, playback_callback, NULL);
/* 初始填充缓冲区 */
snd_pcm_sframes_t avail = snd_pcm_avail_update(pcm);
while (avail >= period_size) {
read(fd, buffer, period_size * 4);
snd_pcm_writei(pcm, buffer, period_size);
avail = snd_pcm_avail_update(pcm);
}
/* 取消 SIGIO 屏蔽,开始异步播放 */
sigprocmask(SIG_UNBLOCK, &sset, NULL);
printf("异步播放中...按回车键退出\n");
getchar();
/* 清理 */
sigprocmask(SIG_BLOCK, &sset, NULL);
snd_pcm_close(pcm);
free(buffer);
close(fd);
return 0;
}
十二、【开发优化】Poll 方式编程(PCM 播放/录音)(参考:pcm_*_poll.c)
(一) 前言:看上面的《数据传输流程》
(二) 什么是 Poll 方式?
使用 poll() 系统调用监听 PCM 设备的文件描述符,当设备准备好时返回。
应用程序 → poll(fds) → 阻塞等待
↓
设备准备好 → poll() 返回 → 读写数据
(三) Poll 编程步骤
1. 获取文件描述符数量
int count;
count = snd_pcm_poll_descriptors_count(pcm);
if (count <= 0) {
fprintf(stderr, "无效的 poll 描述符数量\n");
return -1;
}
printf("需要 %d 个 poll 描述符\n", count);
2. 获取文件描述符
struct pollfd *fds;
/* 分配 pollfd 数组 */
fds = malloc(sizeof(struct pollfd) * count);
/* 获取文件描述符 */
snd_pcm_poll_descriptors(pcm, fds, count);
3. 使用 poll 等待
int ret;
while (1) {
/* 等待设备准备好 */
ret = poll(fds, count, -1); // -1 表示无限等待
if (ret < 0) {
perror("poll 失败");
break;
}
/* 检查事件 */
unsigned short revents;
snd_pcm_poll_descriptors_revents(pcm, fds, count, &revents);
if (revents & POLLOUT) {
/* 可以写入数据(播放) */
snd_pcm_writei(pcm, buffer, period_size);
}
if (revents & POLLIN) {
/* 可以读取数据(录音) */
snd_pcm_readi(pcm, buffer, period_size);
}
if (revents & POLLERR) {
/* 发生错误 */
fprintf(stderr, "设备出错\n");
break;
}
}
(四) 完整 Poll 播放示例
/***************************************************************
* PCM Poll 方式播放示例
***************************************************************/
#include
#include
#include
#include
int main(int argc, char *argv[])
{
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hwparams;
struct pollfd *fds;
unsigned char buffer[8192];
snd_pcm_uframes_t period_size = 1024;
int count, fd, ret;
unsigned short revents;
/* 打开音频文件 */
fd = open(argv[1], O_RDONLY);
/* 打开并配置 PCM(省略) */
// ...
/* 获取 poll 描述符 */
count = snd_pcm_poll_descriptors_count(pcm);
fds = malloc(sizeof(struct pollfd) * count);
snd_pcm_poll_descriptors(pcm, fds, count);
printf("使用 poll 方式播放...\n");
/* 循环播放 */
while (1) {
/* 等待设备准备好 */
ret = poll(fds, count, -1);
if (ret < 0) {
perror("poll 失败");
break;
}
/* 检查事件 */
snd_pcm_poll_descriptors_revents(pcm, fds, count, &revents);
if (revents & POLLOUT) {
/* 可以写入 */
ret = read(fd, buffer, period_size * 4);
if (ret <= 0)
break;
snd_pcm_writei(pcm, buffer, period_size);
}
if (revents & POLLERR) {
fprintf(stderr, "设备错误\n");
break;
}
}
/* 清理 */
free(fds);
snd_pcm_close(pcm);
close(fd);
return 0;
}
十三、【开发记录】PCM 设备状态管理(参考:pcm_playback_ctl.c)
(一) PCM 设备状态
PCM 设备有多种状态:
typedef enum {
SND_PCM_STATE_OPEN, // 打开
SND_PCM_STATE_SETUP, // 已设置
SND_PCM_STATE_PREPARED, // 已准备
SND_PCM_STATE_RUNNING, // 运行中
SND_PCM_STATE_XRUN, // 下溢/上溢
SND_PCM_STATE_DRAINING, // 排空中
SND_PCM_STATE_PAUSED, // 已暂停
SND_PCM_STATE_SUSPENDED, // 已挂起
SND_PCM_STATE_DISCONNECTED // 已断开
} snd_pcm_state_t;
(二) 状态转换图
打开设备
↓
OPEN → 设置参数 → SETUP → 准备 → PREPARED
↓
开始写入/读取
↓
RUNNING
↓
┌──────────────┬───────────┴───────────┬──────────┐
↓ ↓ ↓ ↓
PAUSED XRUN DRAINING 结束
(暂停) (错误) (排空)
↓ ↓ ↓
恢复 → 恢复 → PREPARED ↓
↓ ↓ ↓
RUNNING RUNNING SETUP
┌─────────────┐
│ OPEN │ ← snd_pcm_open()
└──────┬──────┘
│
↓ snd_pcm_hw_params()
┌─────────────┐
│ SETUP │ ← 配置完成,准备就绪
└──────┬──────┘
│
↓ snd_pcm_prepare()
┌─────────────┐
│ PREPARED │ ← 准备好开始传输数据
└──────┬──────┘
│
↓ 开始读/写数据
┌─────────────┐
│ RUNNING │ ← 正在传输音频数据
└──────┬──────┘
│
↓ 缓冲区空/满
┌─────────────┐
│ XRUN │ ← 欠载(underrun)/过载(overrun)
└──────┬──────┘
│
↓ snd_pcm_drop() / snd_pcm_drain()
┌─────────────┐
│ SETUP │
└─────────────┘
(三) 状态相关 API
1. 获取当前状态
snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm);
// 使用示例
snd_pcm_state_t state = snd_pcm_state(pcm);
switch (state) {
case SND_PCM_STATE_RUNNING:
printf("正在运行\n");
break;
case SND_PCM_STATE_XRUN:
printf("发生 XRUN(缓冲区下溢/上溢)\n");
break;
case SND_PCM_STATE_PAUSED:
printf("已暂停\n");
break;
default:
printf("其他状态: %d\n", state);
}
2. 准备设备
int snd_pcm_prepare(snd_pcm_t *pcm);
// 使用示例:从 XRUN 恢复
if (snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
snd_pcm_prepare(pcm); // 重新准备设备
}
3. 暂停/恢复
int snd_pcm_pause(snd_pcm_t *pcm, int enable);
// 暂停
snd_pcm_pause(pcm, 1);
// 恢复
snd_pcm_pause(pcm, 0);
// 使用示例
if (snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING) {
snd_pcm_pause(pcm, 1); // 暂停
}
else if (snd_pcm_state(pcm) == SND_PCM_STATE_PAUSED) {
snd_pcm_pause(pcm, 0); // 恢复
}
4. 停止设备
/* 立即停止(丢弃缓冲区数据) */
int snd_pcm_drop(snd_pcm_t *pcm);
/* 排空缓冲区后停止(等待数据播放完) */
int snd_pcm_drain(snd_pcm_t *pcm);
// 使用示例
snd_pcm_drain(pcm); // 等待播放完毕
snd_pcm_close(pcm); // 关闭设备
(四) 错误处理和恢复
/**
* @brief PCM 写入时的错误处理
*/
int pcm_write_with_recovery(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t frames)
{
snd_pcm_sframes_t ret;
ret = snd_pcm_writei(pcm, buffer, frames);
if (ret < 0) {
/* 发生错误 */
switch (ret) {
case -EPIPE:
/* 缓冲区下溢(Underrun) */
fprintf(stderr, "缓冲区下溢,正在恢复...\n");
ret = snd_pcm_prepare(pcm); // 重新准备
if (ret < 0) {
fprintf(stderr, "无法从下溢恢复: %s\n", snd_strerror(ret));
return ret;
}
/* 重新写入 */
ret = snd_pcm_writei(pcm, buffer, frames);
break;
case -ESTRPIPE:
/* 设备被挂起 */
fprintf(stderr, "设备被挂起,等待恢复...\n");
while ((ret = snd_pcm_resume(pcm)) == -EAGAIN)
sleep(1); // 等待恢复
if (ret < 0) {
ret = snd_pcm_prepare(pcm);
if (ret < 0) {
fprintf(stderr, "无法恢复: %s\n", snd_strerror(ret));
return ret;
}
}
/* 重新写入 */
ret = snd_pcm_writei(pcm, buffer, frames);
break;
default:
fprintf(stderr, "写入错误: %s\n", snd_strerror(ret));
return ret;
}
}
return ret;
}
十四、【开发记录】混音器(Mixer)控制:音频设备的控制接口,用于管理声卡的各种音频参数和设置(参考:pcm_playback_mixer.c)
(一) 混音器简介
混音器(Mixer)用于控制音频设备的音量、静音、输入选择等。
混音器结构:
┌─────────────────────────────────────┐
│ Mixer Handle │
│ (snd_mixer_t *mixer) │
└───────────────┬─────────────────────┘
│
┌───────┴────────┬────────────┐
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│ 元素1 │ │ 元素2 │ │ 元素3 │
│Playback│ │Capture │ │ Mixer │
│ Volume │ │ Volume │ │ Mute │
└────────┘ └────────┘ └────────┘
(二) 混音器编程步骤
1. 打开混音器
snd_mixer_t *mixer;
int ret;
ret = snd_mixer_open(&mixer, 0);
if (ret < 0) {
fprintf(stderr, "打开混音器失败: %s\n", snd_strerror(ret));
return -1;
}
2. Attach 关联声卡控制设备
ret = snd_mixer_attach(mixer, "hw:0"); // 关联声卡0
if (ret < 0) {
fprintf(stderr, "关联声卡失败: %s\n", snd_strerror(ret));
snd_mixer_close(mixer);
return -1;
}
3. 注册混音器
ret = snd_mixer_selem_register(mixer, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "注册混音器失败: %s\n", snd_strerror(ret));
snd_mixer_close(mixer);
return -1;
}
4. 加载混音器
ret = snd_mixer_load(mixer);
if (ret < 0) {
fprintf(stderr, "加载混音器失败: %s\n", snd_strerror(ret));
snd_mixer_close(mixer);
return -1;
}
5. 遍历元素
snd_mixer_elem_t *elem;
/* 获取第一个元素 */
elem = snd_mixer_first_elem(mixer);
while (elem) {
/* 获取元素名称 */
const char *name = snd_mixer_selem_get_name(elem);
printf("元素: %s\n", name);
/* 检查是否支持播放音量控制 */
if (snd_mixer_selem_has_playback_volume(elem)) {
long min, max, vol;
/* 获取音量范围 */
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
printf(" 音量范围: %ld - %ld\n", min, max);
/* 获取当前音量 */
snd_mixer_selem_get_playback_volume(elem,
SND_MIXER_SCHN_FRONT_LEFT,
&vol);
printf(" 当前音量: %ld\n", vol);
}
/* 下一个元素 */
elem = snd_mixer_elem_next(elem);
}
6. 【获取/更改元素的配置值】设置音量
snd_mixer_elem_t *elem;
long min, max, vol;
/* 找到播放音量元素(通常名为 "Master" 或 "PCM") */
snd_mixer_selem_id_t *sid;
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_name(sid, "Master");
snd_mixer_selem_id_set_index(sid, 0);
elem = snd_mixer_find_selem(mixer, sid);
if (!elem) {
fprintf(stderr, "未找到 Master 元素\n");
return -1;
}
/* 获取音量范围 */
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
/* 设置音量为 80% */
vol = (max - min) * 0.8 + min;
snd_mixer_selem_set_playback_volume_all(elem, vol);
printf("音量已设置为: %ld (范围: %ld-%ld)\n", vol, min, max);
7. 【获取/更改元素的配置值】静音控制
/* 静音 */
snd_mixer_selem_set_playback_switch_all(elem, 0);
/* 取消静音 */
snd_mixer_selem_set_playback_switch_all(elem, 1);
/* 检查是否静音 */
int val;
snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &val);
printf("静音状态: %s\n", val ? "未静音" : "已静音");
8. 关闭混音器
snd_mixer_close(mixer);
(三) 完整混音器示例
/***************************************************************
* 混音器控制示例
***************************************************************/
#include
#include
#include
int main(void)
{
snd_mixer_t *mixer;
snd_mixer_elem_t *elem;
snd_mixer_selem_id_t *sid;
long min, max, vol;
int ret;
/* 1. 打开混音器 */
snd_mixer_open(&mixer, 0);
/* 2. 关联声卡 */
snd_mixer_attach(mixer, "hw:0");
/* 3. 注册 */
snd_mixer_selem_register(mixer, NULL, NULL);
/* 4. 加载 */
snd_mixer_load(mixer);
/* 5. 查找 Master 元素 */
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_name(sid, "Master");
snd_mixer_selem_id_set_index(sid, 0);
elem = snd_mixer_find_selem(mixer, sid);
if (!elem) {
fprintf(stderr, "未找到 Master 控制\n");
snd_mixer_close(mixer);
return -1;
}
/* 6. 获取音量范围 */
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
printf("音量范围: %ld - %ld\n", min, max);
/* 7. 获取当前音量 */
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &vol);
printf("当前音量: %ld\n", vol);
/* 8. 设置音量为 50% */
vol = (max - min) / 2 + min;
snd_mixer_selem_set_playback_volume_all(elem, vol);
printf("音量已设置为 50%%: %ld\n", vol);
/* 9. 取消静音 */
snd_mixer_selem_set_playback_switch_all(elem, 1);
printf("已取消静音\n");
/* 10. 关闭 */
snd_mixer_close(mixer);
return 0;
}
十五、【扩展知识】WAV 文件格式
(一) WAV 文件结构
┌─────────────────────────────────────┐
│ RIFF Chunk (12字节) │
│ - ChunkID: "RIFF" │
│ - ChunkSize: 文件大小-8 │
│ - Format: "WAVE" │
├─────────────────────────────────────┤
│ fmt Chunk (24字节) │
│ - Subchunk1ID: "fmt " │
│ - Subchunk1Size: 16 (PCM) │
│ - AudioFormat: 1 (PCM) │
│ - NumChannels: 声道数(1/2) │
│ - SampleRate: 采样率(44100等) │
│ - ByteRate: 字节率 │
│ - BlockAlign: 块对齐 │
│ - BitsPerSample: 位深度(16等) │
├─────────────────────────────────────┤
│ data Chunk │
│ - Subchunk2ID: "data" │
│ - Subchunk2Size: 数据大小 │
│ - 音频数据... │
└─────────────────────────────────────┘
(二) WAV 文件解析代码
/***************************************************************
* WAV 文件格式定义
***************************************************************/
#include
/* RIFF chunk */
typedef struct {
char ChunkID[4]; // "RIFF"
uint32_t ChunkSize; // 文件大小 - 8
char Format[4]; // "WAVE"
} __attribute__((packed)) WAV_RIFF;
/* fmt chunk */
typedef struct {
char Subchunk1ID[4]; // "fmt "
uint32_t Subchunk1Size; // 16 for PCM
uint16_t AudioFormat; // 1 = PCM
uint16_t NumChannels; // 1 = Mono, 2 = Stereo
uint32_t SampleRate; // 8000, 44100, 48000, etc.
uint32_t ByteRate; // SampleRate * NumChannels * BitsPerSample/8
uint16_t BlockAlign; // NumChannels * BitsPerSample/8
uint16_t BitsPerSample; // 8, 16, 24, 32
} __attribute__((packed)) WAV_FMT;
/* data chunk */
typedef struct {
char Subchunk2ID[4]; // "data"
uint32_t Subchunk2Size; // 数据大小
} __attribute__((packed)) WAV_DATA;
/**
* @brief 解析 WAV 文件
*/
int parse_wav_file(const char *filename, WAV_FMT *fmt)
{
FILE *fp;
WAV_RIFF riff;
WAV_DATA data;
fp = fopen(filename, "rb");
if (!fp) {
perror("打开文件失败");
return -1;
}
/* 读取 RIFF chunk */
fread(&riff, sizeof(WAV_RIFF), 1, fp);
if (strncmp(riff.ChunkID, "RIFF", 4) != 0 ||
strncmp(riff.Format, "WAVE", 4) != 0) {
fprintf(stderr, "不是有效的 WAV 文件\n");
fclose(fp);
return -1;
}
/* 读取 fmt chunk */
fread(fmt, sizeof(WAV_FMT), 1, fp);
if (strncmp(fmt->Subchunk1ID, "fmt ", 4) != 0) {
fprintf(stderr, "WAV 文件格式错误\n");
fclose(fp);
return -1;
}
/* 打印信息 */
printf("========== WAV 文件信息 ==========\n");
printf("文件名: %s\n", filename);
printf("格式: %s\n", fmt->AudioFormat == 1 ? "PCM" : "非PCM");
printf("声道数: %d\n", fmt->NumChannels);
printf("采样率: %d Hz\n", fmt->SampleRate);
printf("位深度: %d 位\n", fmt->BitsPerSample);
printf("字节率: %d 字节/秒\n", fmt->ByteRate);
printf("块对齐: %d 字节\n", fmt->BlockAlign);
printf("==================================\n");
/* 跳到 data chunk */
fseek(fp, sizeof(WAV_RIFF) + 8 + fmt->Subchunk1Size, SEEK_SET);
fread(&data, sizeof(WAV_DATA), 1, fp);
if (strncmp(data.Subchunk2ID, "data", 4) != 0) {
fprintf(stderr, "未找到 data chunk\n");
fclose(fp);
return -1;
}
printf("数据大小: %d 字节\n", data.Subchunk2Size);
fclose(fp);
return 0;
}
十六、【扩展开发】其他开发记录
(一) 示例1:简单的 WAV 播放器
/***************************************************************
* 简单的 WAV 播放器
* 编译: gcc -o player wav_player.c -lasound
* 运行: ./player music.wav
***************************************************************/
#include
#include
#include
#include
#include
#include
/* WAV 格式定义(省略,见上文) */
// ...
static snd_pcm_t *pcm = NULL;
static WAV_FMT wav_fmt;
static unsigned char *buffer = NULL;
static int fd = -1;
static snd_pcm_uframes_t period_size = 1024;
/* 初始化 PCM */
int pcm_init(void)
{
snd_pcm_hw_params_t *hwparams;
int ret;
/* 打开设备 */
ret = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0) {
fprintf(stderr, "打开 PCM 失败: %s\n", snd_strerror(ret));
return -1;
}
/* 分配并初始化参数对象 */
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm, hwparams);
/* 设置参数 */
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
snd_pcm_hw_params_set_periods(pcm, hwparams, 16, 0);
/* 应用参数 */
ret = snd_pcm_hw_params(pcm, hwparams);
snd_pcm_hw_params_free(hwparams);
if (ret < 0) {
fprintf(stderr, "设置参数失败: %s\n", snd_strerror(ret));
snd_pcm_close(pcm);
return -1;
}
return 0;
}
/* 播放 */
void play(void)
{
int buf_bytes = period_size * wav_fmt.BlockAlign;
snd_pcm_sframes_t ret;
buffer = malloc(buf_bytes);
if (!buffer) {
perror("分配内存失败");
return;
}
printf("开始播放...\n");
while (1) {
ret = read(fd, buffer, buf_bytes);
if (ret <= 0)
break; // 文件读取完毕
ret = snd_pcm_writei(pcm, buffer, period_size);
if (ret < 0) {
fprintf(stderr, "写入失败: %s\n", snd_strerror(ret));
if (ret == -EPIPE) {
snd_pcm_prepare(pcm); // 从 underrun 恢复
continue;
}
break;
}
}
printf("播放完成!\n");
free(buffer);
}
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("用法: %s \n", argv[0]);
return -1;
}
/* 解析 WAV 文件 */
if (parse_wav_file(argv[1], &wav_fmt) < 0)
return -1;
/* 打开文件 */
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("打开文件失败");
return -1;
}
/* 跳过 WAV 头部 */
lseek(fd, sizeof(WAV_RIFF) + 8 + wav_fmt.Subchunk1Size + 8, SEEK_SET);
/* 初始化 PCM */
if (pcm_init() < 0) {
close(fd);
return -1;
}
/* 播放 */
play();
/* 清理 */
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
close(fd);
return 0;
}
(二) 示例2:简单的录音程序
/***************************************************************
* 简单的录音程序
* 编译: gcc -o recorder recorder.c -lasound
* 运行: ./recorder output.raw 10
* (录音10秒)
***************************************************************/
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hwparams;
unsigned char buffer[8192];
snd_pcm_uframes_t period_size = 1024;
int fd, duration, ret;
int total_frames, recorded_frames = 0;
if (argc != 3) {
printf("用法: %s <输出文件> <录音时长(秒)>\n", argv[0]);
return -1;
}
duration = atoi(argv[2]);
total_frames = 44100 * duration; // 假设44.1kHz采样率
/* 打开输出文件 */
fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("打开文件失败");
return -1;
}
/* 打开 PCM 设备(录音) */
ret = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_CAPTURE, 0);
if (ret < 0) {
fprintf(stderr, "打开设备失败: %s\n", snd_strerror(ret));
close(fd);
return -1;
}
/* 设置参数 */
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm, hwparams);
snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate(pcm, hwparams, 44100, 0);
snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
snd_pcm_hw_params_set_periods(pcm, hwparams, 16, 0);
snd_pcm_hw_params(pcm, hwparams);
snd_pcm_hw_params_free(hwparams);
printf("开始录音 %d 秒...\n", duration);
/* 录音循环 */
while (recorded_frames < total_frames) {
ret = snd_pcm_readi(pcm, buffer, period_size);
if (ret < 0) {
fprintf(stderr, "读取失败: %s\n", snd_strerror(ret));
if (ret == -EPIPE) {
snd_pcm_prepare(pcm); // 从 overrun 恢复
continue;
}
break;
}
/* 写入文件 */
write(fd, buffer, ret * 4); // 16位双声道 = 4字节/帧
recorded_frames += ret;
/* 显示进度 */
printf("\r录音进度: %d%%", recorded_frames * 100 / total_frames);
fflush(stdout);
}
printf("\n录音完成!\n");
/* 清理 */
snd_pcm_close(pcm);
close(fd);
return 0;
}
十七、 快速参考表
(一) 常用 API 速查
功能 | 函数 | 说明 |
打开设备 |
| 打开 PCM 设备 |
设置参数 |
| 应用硬件参数 |
写入数据 |
| 播放音频(交错模式) |
读取数据 |
| 录音(交错模式) |
获取状态 |
| 获取设备状态 |
暂停 |
| 暂停/恢复播放 |
准备 |
| 准备设备 |
排空 |
| 等待数据播放完 |
停止 |
| 立即停止 |
关闭 |
| 关闭设备 |
(二) 常用设备名称
设备名 | 说明 |
| 默认设备(推荐) |
| 声卡0设备0(直接访问) |
| 通过插件访问(自动转换) |
| 空设备(测试用) |
(三) 常用采样率
采样率 | 用途 |
8000 Hz | 电话质量 |
16000 Hz | 语音 |
22050 Hz | FM收音机质量 |
44100 Hz | CD音质(常用) |
48000 Hz | DVD/专业音频 |
96000 Hz | 高清音频 |
(四) 常用格式
格式 | 位深 | 说明 |
| 16位 | 有符号16位小端(最常用) |
| 24位 | 有符号24位小端 |
| 32位 | 有符号32位小端 |
| 8位 | 无符号8位 |
十八、 常见问题
(一) 如何播放 MP3 文件?
ALSA 只支持 PCM 格式,需要先解码:
# 使用 mpg123 解码
mpg123 -w output.wav input.mp3
# 使用 ffmpeg 转换
ffmpeg -i input.mp3 output.wav
(二) 缓冲区下溢(Underrun)
原因: 应用程序写入数据速度慢于硬件消耗速度
解决:
- 增大缓冲区(增加 periods)
- 使用异步或 poll 方式
- 优化数据读取性能
(三) 缓冲区上溢(Overrun)
原因: 录音时,应用程序读取速度慢于硬件录制速度
解决:
- 增大缓冲区
- 及时读取数据
(四) 设备忙(Device busy)
原因: 设备已被其他程序占用
解决:
# 查看占用进程
fuser -v /dev/snd/*
# 杀死占用进程
sudo killall pulseaudio
(五) 权限问题
# 将用户加入 audio 组
sudo usermod -a -G audio $USER
# 重新登录生效
十九、 调试工具
(一) aplay / arecord
# 播放 WAV 文件
aplay test.wav
# 播放 RAW 文件(需指定参数)
aplay -f S16_LE -r 44100 -c 2 test.raw
# 录音 10 秒
arecord -d 10 -f cd -t wav output.wav
(二) amixer
# 显示所有控制
amixer
# 显示特定控制
amixer sget Master
# 设置音量(0-100%)
amixer sset Master 80%
# 静音
amixer sset Master mute
# 取消静音
amixer sset Master unmute
(三) speaker-test
# 测试左右声道
speaker-test -t wav -c 2

浙公网安备 33010602011771号