当前位置: 首页 > news >正文

【嵌入式Linux - 应用开发】音频(ALSA 框架)

【目录】

【📚 参考资源】

【总结】

(一) ALSA 中的"音频设备"= 一套完整的音频子系统

(二) 概念区分表格

一、【库介绍】ALSA 框架概述(Linux 系统的标准音频框架,是驱动 + API + 工具的完整音频子系统)

(一) 什么是 ALSA?

(二) ALSA 的三个主要组件

二、【库介绍】alsa-lib 库概述(ALSA 的用户空间库中的一套 C 语言 API)

(一) 什么是 alsa-lib?

(二) 主要功能模块

1. PCM(Pulse Code Modulation)接口:用于音频数据的采集(录音)和播放

2. Mixer(混音器)接口:用于音量调节、静音、输入源选择

3. Control(控制)接口:对声卡设备的管理和参数查询

4. 其他常用功能模块

一、Raw MIDI 接口

二、Sequencer(音序器)接口

三、Timer 接口

四、HW Dep(硬件依赖)接口

【总结】

(三) alsa-lib 编译选项

三、【库介绍】alsa-lib 库移植以及 alsa-utils 工具的使用

(一) alsa-lib 库移植:一般移植 alsa-lib 和 alsa-utils 两个库

(二) alas-utils 工具库使用

1. aplay:用于播放音频文件,支持 WAV、RAW 等 PCM 格式。

2. arecord:用于录音测试,支持 WAV 与 RAW 格式。

3. amixer:是命令行混音器,用于查看和设置声卡控件。

4. alsamixer:提供交互式界面,方便调节音量和输入输出源。

5. alsactl:用于保存和恢复声卡配置,实现音量与路由的持久化。

四、【基础知识】音频子系统/音频设备架构详解(音频设备、I2S & ALSA)

(一) 什么是音频设备?实现声音采集、处理与播放的一套硬件或虚拟装置,如声卡+麦克风+扬声器等

(二) 完整的音频硬件架构(音频系统通常由输入端(麦克风/线路)、编解码器、数字接口、处理单元和输出端(扬声器/耳机)组成)

【补充】I2S 与 ALSA 关系

【补充】硬件层面的音频接口(包括 I2S、USB Audio 等,用于传输数字或模拟音频信号)

1. I2S(Inter-IC Sound)

一、I2S 是什么?

二、I2S 传输示例

2. 其他常见音频接口

一、PCM/I2S 接口

二、SPDIF(Sony/Philips Digital Interface)

三、AC97/HD-Audio

四、(UAC)USB Audio

五、【开发基础】ASLA 框架驱动部分的 sound 设备节点 & ALSA 框架驱动的声卡与设备

(一) 【设备节点】/dev/snd/ 目录结构以及详解

1. 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

六、【调试方式】命令行、alsa-utils 工具

(一) ALSA 框架驱动部分

(二) alsa-utils 工具(查看音频设备信息)

七、【开发基础】ALSA & PCM 基础知识

(一) 核心概念图解

(二) 样本长度(Sample / Bit Depth)

1. 什么是样本?

2. 样本长度(位深度)

3. 数值示例

(三) 声道数(Channel)

1. 什么是声道?

2. 常用声道配置

(四) 帧(Frame)

1. 什么是帧?

2. 帧大小计算

3. 在 ALSA 中

(五) 采样率(Sample Rate)

1. 什么是采样率?

2. 常用采样率对照表

3. 计算示例

(六) 交错模式(Interleaved)

1. 两种数据存储方式

一、交错模式(Interleaved)⭐ 常用

二、非交错模式(Non-interleaved)

2. ALSA 代码示例

(七) 周期(Period)

1. 什么是周期?

2. 周期的作用

3. 周期大小的影响

4. 计算示例

(八) 缓冲区(Buffer)

1. 什么是缓冲区?

2. 环形缓冲区可视化

3. Buffer/Period/Frame/Sample 关系图

(九) 【核心】数据传输流程(PCM 播放/录音)

1. 播放(Playback)

2. 录音(Capture)

3. 完整数据流

(十) Over Run 和 Under Run(XRUN)

1. Under Run(下溢)- 播放时

2. Over Run(上溢)- 录音时

3. XRUN 恢复

(十一) 一图看懂所有概念

八、【相关 API】alsa-lib 库 API 介绍

(一) PCM 设备管理

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 设备。

(二) 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 大小。

(三) PCM 软件参数配置

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 - 设置可用空间的最小值,用于唤醒等待的应用程序。

(四) PCM 数据传输

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 设备变为可读或可写状态。

(五) PCM 状态查询

1. snd_pcm_state - 获取 PCM 设备的当前状态。

2. snd_pcm_avail - 获取当前可用的帧数。

3. snd_pcm_avail_update - 更新并获取当前可用的帧数。

4. snd_pcm_delay - 获取硬件延迟(尚未播放/录制的帧数)。

(六) PCM 异步处理

1. snd_async_add_pcm_handler - 为 PCM 设备添加异步回调处理器。

2. snd_async_handler_get_pcm - 从异步处理器获取 PCM 句柄。

(七) Mixer 控制(音量控制)

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 - 将帧数转换为字节数。

(九) Control 接口(高级控制)

1. snd_ctl_open - 打开控制接口。

2. snd_ctl_close - 关闭控制接口。

3. snd_ctl_card_info - 获取声卡信息。

4. snd_card_next - 获取下一个声卡索引。

5. snd_card_get_name - 获取声卡名称。

6. snd_card_get_longname - 获取声卡完整名称。

(十) XRUN 处理(欠载/溢出)

1. snd_pcm_recover - 从错误状态恢复 PCM 设备。

(十一) 常用数据类型

1. snd_pcm_uframes_t

2. snd_pcm_sframes_t

3. snd_pcm_t

4. snd_pcm_hw_params_t

5. snd_pcm_sw_params_t

6. snd_mixer_t

7. snd_mixer_elem_t

8. snd_ctl_t

(十二) 典型使用流程

1. 播放音频流程

2. 录音音频流程

3. Mixer 音量控制流程

九、【开发记录】PCM 播放详解(播放 WAV 文件)

(一) PCM 播放流程

(二) 关键 API 函数

1. 打开 PCM 设备

2. 设置硬件参数

3. 写入音频数据

4. 关闭设备

(三) 完整播放示例

十、【开发记录】PCM 录音详解(录制 PCM 数据,得到 PCM 文件)

(一) PCM 录音流程

(二) 录音 API

(三) 完整录音示例

十一、【开发优化】异步方式编程(PCM 播放/录音)(参考:pcm_*_async.c)

(一) 【前言】数据传输流程(播放/录音):应用程序把音频数据写入内核,内核处理传入硬件(这个过程需要时间)

1. 播放(Playback)

2. 录音(Capture)

3. 完整数据流

(二) 什么是异步方式?

(三) 异步编程步骤

1. 屏蔽 SIGIO 信号

2. 注册异步回调函数

3. 取消信号屏蔽

(四) 完整异步播放示例

十二、【开发优化】Poll 方式编程(PCM 播放/录音)(参考:pcm_*_poll.c)

(一) 前言:看上面的《数据传输流程》

(二) 什么是 Poll 方式?

(三) Poll 编程步骤

1. 获取文件描述符数量

2. 获取文件描述符

3. 使用 poll 等待

(四) 完整 Poll 播放示例

十三、【开发记录】PCM 设备状态管理(参考:pcm_playback_ctl.c)

(一) PCM 设备状态

(二) 状态转换图

(三) 状态相关 API

1. 获取当前状态

2. 准备设备

3. 暂停/恢复

4. 停止设备

(四) 错误处理和恢复

十四、【开发记录】混音器(Mixer)控制:音频设备的控制接口,用于管理声卡的各种音频参数和设置(参考:pcm_playback_mixer.c)

(一) 混音器简介

(二) 混音器编程步骤

1. 打开混音器

2. Attach 关联声卡控制设备

3. 注册混音器

4. 加载混音器

5. 遍历元素

6. 【获取/更改元素的配置值】设置音量

7. 【获取/更改元素的配置值】静音控制

8. 关闭混音器

(三) 完整混音器示例

十五、【扩展知识】WAV 文件格式

(一) WAV 文件结构

(二) WAV 文件解析代码

十六、【扩展开发】其他开发记录

(一) 示例1:简单的 WAV 播放器

(二) 示例2:简单的录音程序

十七、📊 快速参考表

(一) 常用 API 速查

(二) 常用设备名称

(三) 常用采样率

(四) 常用格式

十八、📝 常见问题

(一) 如何播放 MP3 文件?

(二) 缓冲区下溢(Underrun)

(三) 缓冲区上溢(Overrun)

(四) 设备忙(Device busy)

(五) 权限问题

十九、🔧 调试工具

(一) aplay / arecord

(二) amixer

(三) speaker-test


【📚 参考资源】

教程主要参考正点原子

  • ALSA 官方文档:

https://www.alsa-project.org/

  • 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 设备节点

/dev/snd/pcmC0D0p

ALSA 设备

逻辑设备名

default, hw:0,0

驱动层

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)

/usr/lib/libasound.so

ALSA Utilities

命令行工具

aplay, arecord, amixer

应用程序层↓
ALSA Library (alsa-lib)↓
ALSA Core (内核)↓
声卡硬件驱动↓
🔊 音频硬件 🎤

二、【库介绍】alsa-lib 库概述(ALSA 的用户空间库中的一套 C 语言 API)

ALSA 提供一套标准 API -> alsa-lib

(一) 什么是 alsa-lib?

alsa-lib 是 ALSA 的用户空间库,提供了一套 C 语言 API,让应用程序可以方便地访问音频设备。

(二) 主要功能模块

#include <alsa/asoundlib.h>  // 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 消息。
  • 关键 APIsnd_rawmidi_open()snd_rawmidi_read()snd_rawmidi_write()
  • 应用场景:电子乐器、MIDI 控制器、音序器。
二、Sequencer(音序器)接口
  • 作用:比 Raw MIDI 更高级,提供事件调度、时间戳、路由。
  • 关键 APIsnd_seq_open()snd_seq_event_output()
  • 应用场景:MIDI 编曲软件、虚拟乐器、复杂 MIDI 路由。
三、Timer 接口
  • 作用:访问声卡或系统的定时器,用于音频事件同步。
  • 关键 APIsnd_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-libalsa-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 muteunmute
  • 录音源选择amixer -c 0 sset 'Input Source' Mic 选择麦克风作为输入源。

4. alsamixer:提供交互式界面,方便调节音量和输入输出源。

  • 启动alsamixeralsamixer -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 关系

可以理解为:

  1. 硬件层
    • SoC 内部有 I²S 控制器(CPU DAI)。
    • 外部有 Codec 芯片(带 ADC/DAC)。
    • 两者通过 I²S 总线传输 PCM 数据
    • Codec 芯片输出模拟信号到扬声器灯
  1. 内核层(ASoC 框架)
    • Platform 驱动:管理 DMA + I²S 控制器。
    • Codec 驱动:管理外部 Codec 芯片(音量、增益、ADC/DAC)。
    • Machine 驱动:把 I²S 控制器和 Codec 绑定,定义音频路由和时钟关系。
    • ASoC Core:把这些组合成 ALSA 的“声卡”,在 /dev/snd/ 下导出 pcmC0D0ppcmC0D0c 等节点。
  1. 用户空间
    • 应用层通过 alsa-lib API(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/cardsdevices:
列出系统中所有声卡注册的设备,包括 control、 pcm、 timer、 seq 等等。如下所示:
cat /proc/asound/devicespcm:
列出系统中的所有 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                                   pointerP0-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 表示 XRUNsnd_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")

  • "hw:0,0" → 声卡0,设备0(直接访问硬件)
  • "hw:1,2" → 声卡1,设备2
  • "default" → 默认设备(推荐,会经过 ALSA 插件处理)
  • "plughw:0,0" → 通过插件访问(自动进行采样率转换等)

stream

snd_pcm_stream_t

流类型:

  • SND_PCM_STREAM_PLAYBACK(播放)
  • SND_PCM_STREAM_CAPTURE(录音)

mode

int

打开模式:

  • 通常为 0(阻塞模式)
  • SND_PCM_NONBLOCK(非阻塞模式)

返回值:成功返回 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 <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <alsa/asoundlib.h>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 <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <alsa/asoundlib.h>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 <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <alsa/asoundlib.h>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 <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>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 <stdint.h>/* RIFF chunk */
typedef struct {char     ChunkID[4];      // "RIFF"uint32_t ChunkSize;       // 文件大小 - 8char     Format[4];       // "WAVE"
} __attribute__((packed)) WAV_RIFF;/* fmt chunk */
typedef struct {char     Subchunk1ID[4];  // "fmt "uint32_t Subchunk1Size;   // 16 for PCMuint16_t AudioFormat;     // 1 = PCMuint16_t NumChannels;     // 1 = Mono, 2 = Stereouint32_t SampleRate;      // 8000, 44100, 48000, etc.uint32_t ByteRate;        // SampleRate * NumChannels * BitsPerSample/8uint16_t BlockAlign;      // NumChannels * BitsPerSample/8uint16_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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <alsa/asoundlib.h>/* 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 <wav文件>\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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <alsa/asoundlib.h>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 速查

功能

函数

说明

打开设备

snd_pcm_open()

打开 PCM 设备

设置参数

snd_pcm_hw_params()

应用硬件参数

写入数据

snd_pcm_writei()

播放音频(交错模式)

读取数据

snd_pcm_readi()

录音(交错模式)

获取状态

snd_pcm_state()

获取设备状态

暂停

snd_pcm_pause()

暂停/恢复播放

准备

snd_pcm_prepare()

准备设备

排空

snd_pcm_drain()

等待数据播放完

停止

snd_pcm_drop()

立即停止

关闭

snd_pcm_close()

关闭设备

(二) 常用设备名称

设备名

说明

"default"

默认设备(推荐)

"hw:0,0"

声卡0设备0(直接访问)

"plughw:0,0"

通过插件访问(自动转换)

"null"

空设备(测试用)

(三) 常用采样率

采样率

用途

8000 Hz

电话质量

16000 Hz

语音

22050 Hz

FM收音机质量

44100 Hz

CD音质(常用)

48000 Hz

DVD/专业音频

96000 Hz

高清音频

(四) 常用格式

格式

位深

说明

S16_LE

16位

有符号16位小端(最常用)

S24_LE

24位

有符号24位小端

S32_LE

32位

有符号32位小端

U8

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
http://www.dtcms.com/a/441900.html

相关文章:

  • 获得场景视频API开发(02):H5前端上传视频之Java转 PHP实现方案
  • 枣阳网站建设公司c 在网站开发方面有优势吗
  • SpringMVC中的常用注解及使用方法
  • PyQt6实例_个股收盘价和市盈率TTM
  • Windows 环境下安装 Node.js 和 Vue.js 框架完全指南
  • C语言第3讲:分支和循环(上)—— 程序的“决策”与“重复”之旅
  • 09.Docker compose
  • 梁山专做网站的公司徐州便民信息网
  • HarmonyOS 应用开发深度解析:ArkTS 状态管理与渲染控制的艺术
  • ThreadX全家桶迎来移交Eclipse基金会后的第2次更新,发布V6.4.3版本,更新终于回到正轨
  • 中国工信备案查询网站哪个网站能免费下载
  • 网站图片上传功能怎么做设计网红店铺
  • 保姆级 Docker 入门到进阶
  • 网站建站网站80s隐秘而伟大新网站怎么做谷歌推广呢
  • uv 配置国内镜像加速教程
  • Leetcode 295. 数据流的中位数 堆
  • Go 语言的 channel
  • python包管理器——uv
  • 【LeetCode】92. 反转链表 II
  • LeetCode:90.最长有效括号
  • AI 重塑行业格局:从金融风控到智能制造的深度实践
  • 网站开发公共文件太仓营销型网站建设
  • MSM多标量乘法:策略及挑战
  • 做58一样的网站网站如何在国外推广
  • Vue渲染—深入VNode(h函数、JSX、render函数)
  • GPT_Data_Processing_Tutorial
  • 华为AC+AP无线网络组网与配置指南
  • 交互动效设计
  • 电子商务网站建设与管理相关文献邢台路桥建设总公司没有网站吗
  • Leetcode 3700. Number of ZigZag Arrays II