4. ALSA中的PCM设备模型:PCM设备注册、PCM流(playback/capture)、PCM硬件参数(hw_params)
ALSA 的 PCM 设备模型是音频数据传输的核心抽象。本章深入剖析 PCM 设备的注册流程、Playback/Capture 流的区分机制,以及硬件参数(hw_params)的协商与配置过程。理解这些概念是进行 DMA 传输优化和环形缓冲区设计的基础。
4.1 PCM设备注册流程
在 ALSA 驱动中,PCM 设备的注册遵循一套标准化的步骤。驱动开发者需要填充 snd_pcm_ops 结构体,并调用核心 API 完成注册。
- 分配 PCM 实例: 调用
snd_pcm_new()或snd_pcm_new_internal()创建 PCM 设备实例。 - 设置操作函数集: 填充
snd_pcm_ops结构体,包括open、close、hw_params、prepare、trigger、pointer等回调。 - 设置硬件约束: 通过
snd_pcm_hw_constraint_*系列函数定义硬件支持的参数范围(如采样率、格式、通道数)。 - 注册设备: 调用
snd_card_register()将 PCM 设备注册到 ALSA 核心层,暴露给用户空间。
以下是一个典型的 PCM 设备注册代码片段:
static const struct snd_pcm_ops my_pcm_ops = {
.open = my_pcm_open,
.close = my_pcm_close,
.hw_params = my_pcm_hw_params,
.hw_free = my_pcm_hw_free,
.prepare = my_pcm_prepare,
.trigger = my_pcm_trigger,
.pointer = my_pcm_pointer,
};
static int my_pcm_new(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
int err;
// 1. 分配 PCM 实例,支持 playback 和 capture
err = snd_pcm_new(card, "My PCM Device", device, 1, 1, &pcm);
if (err < 0)
return err;
// 2. 绑定操作函数集
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &my_pcm_ops);
// 3. 设置硬件约束(示例)
snd_pcm_hw_constraint_list(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
0, SNDRV_PCM_HW_PARAM_RATE, &my_rates);
return 0;
}
snd_pcm_new() 的第三个参数 playback_count 和第四个参数 capture_count 分别指定该设备支持的 playback 和 capture 子流数量。如果某个方向不支持,则传入 0。
4.2 PCM流:Playback 与 Capture
ALSA 将 PCM 数据流分为两个方向:Playback(播放) 和 Capture(录音)。每个方向对应一个独立的 snd_pcm_substream 实例,拥有各自的 DMA 缓冲区、硬件参数和状态机。
| 特性 | Playback 流 | Capture 流 |
|---|---|---|
| 数据方向 | 用户空间 → 硬件(DAC) | 硬件(ADC) → 用户空间 |
| DMA 操作 | 内存 → 外设(DMA 写) | 外设 → 内存(DMA 读) |
| 环形缓冲区角色 | 用户填充数据,DMA 消费 | DMA 填充数据,用户消费 |
| 典型应用 | 音频播放器、语音合成 | 录音机、语音识别 |
在驱动内部,snd_pcm_substream 通过 stream 字段区分方向:
struct snd_pcm_substream {
struct snd_pcm *pcm;
struct snd_pcm_str *pstr; // 指向所属的流方向
int number; // 子流编号
int stream; // SNDRV_PCM_STREAM_PLAYBACK 或 SNDRV_PCM_STREAM_CAPTURE
struct snd_pcm_runtime *runtime;
// ... 其他字段
};
snd_pcm 设备,但它们的硬件参数(如采样率、格式)可以独立配置。全双工设备需要确保两个方向的参数兼容,否则可能导致硬件冲突。
下面的 SVG 图展示了 Playback 和 Capture 流在 ALSA 框架中的位置与数据流向:
4.3 PCM硬件参数(hw_params)
hw_params 是 ALSA 驱动中最重要的回调之一。它在用户空间调用 snd_pcm_hw_params() 时被触发,用于协商和配置硬件相关的参数。这些参数直接影响 DMA 传输的效率和音频质量。
4.3.1 关键硬件参数
| 参数 | ALSA 宏定义 | 说明 | 典型值 |
|---|---|---|---|
| 采样率 | SNDRV_PCM_HW_PARAM_RATE |
每秒采样点数 | 44100, 48000, 96000 |
| 采样格式 | SNDRV_PCM_HW_PARAM_FORMAT |
每个采样的位宽和编码 | S16_LE, S24_LE, FLOAT_LE |
| 通道数 | SNDRV_PCM_HW_PARAM_CHANNELS |
音频通道数量 | 1 (Mono), 2 (Stereo), 6 (5.1) |
| 周期大小 | SNDRV_PCM_HW_PARAM_PERIOD_SIZE |
一次中断传输的帧数 | 256, 512, 1024 |
| 周期数 | SNDRV_PCM_HW_PARAM_PERIODS |
环形缓冲区中的周期数量 | 2, 4, 8 |
| 缓冲区大小 | SNDRV_PCM_HW_PARAM_BUFFER_SIZE |
环形缓冲区总帧数 | 1024, 2048, 4096 |
4.3.2 hw_params 回调实现
在驱动中,hw_params 回调负责验证用户请求的参数,并配置硬件寄存器。典型的实现包括:
- 参数验证: 检查请求的采样率、格式、通道数是否在硬件支持范围内。
- DMA 缓冲区分配: 根据
buffer_size和period_size分配 DMA 内存。 - 硬件寄存器配置: 设置 I2S 接口的位时钟、帧时钟、数据格式等。
- DMA 引擎配置: 配置 DMA 传输的源/目的地址、传输宽度、突发长度等。
static int my_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct my_pcm_runtime *prtd = substream->runtime->private_data;
int err;
// 1. 获取参数
unsigned int rate = params_rate(hw_params);
snd_pcm_format_t format = params_format(hw_params);
unsigned int channels = params_channels(hw_params);
unsigned int buffer_size = params_buffer_size(hw_params);
unsigned int period_size = params_period_size(hw_params);
// 2. 验证参数(示例:仅支持 48000Hz)
if (rate != 48000)
return -EINVAL;
// 3. 分配 DMA 缓冲区
err = snd_pcm_lib_malloc_pages(substream, buffer_size *
snd_pcm_format_physical_width(format) / 8 * channels);
if (err < 0)
return err;
// 4. 配置硬件寄存器(伪代码)
my_hw_set_rate(rate);
my_hw_set_format(format);
my_hw_set_channels(channels);
// 5. 保存参数到运行时结构
prtd->period_size = period_size;
prtd->buffer_size = buffer_size;
return 0;
}
hw_params 中尽早分配 DMA 缓冲区,并使用 snd_pcm_lib_malloc_pages() 确保内存对齐。对于嵌入式系统,建议使用 dma_alloc_coherent() 分配非缓存内存,避免 DMA 与 CPU 缓存一致性问题。
4.3.3 硬件参数协商流程
用户空间通过 snd_pcm_hw_params_any() 初始化参数,然后调用 snd_pcm_hw_params_set_* 系列函数设置期望值。ALSA 核心层会调用驱动的 hw_params 回调,并返回最终确定的参数。
下面的 SVG 图展示了从用户空间到驱动的完整参数协商流程:
4.4 硬件约束与参数范围
为了简化用户空间的参数选择,驱动可以通过 硬件约束(Constraints) 定义参数的有效范围。ALSA 提供了多种约束函数:
| 约束函数 | 用途 | 示例 |
|---|---|---|
snd_pcm_hw_constraint_list |
限制参数为离散值列表 | 采样率仅支持 44100, 48000 |
snd_pcm_hw_constraint_minmax |
限制参数在最小/最大值之间 | 缓冲区大小 1024 ~ 65536 |
snd_pcm_hw_constraint_pow2 |
限制参数为 2 的幂 | 周期大小必须是 2^n |
snd_pcm_hw_constraint_step |
限制参数为步长的整数倍 | 通道数必须是 2 的倍数 |
probe 函数中)设置硬件约束,而不是在 hw_params 回调中。这样可以提前告知用户空间支持的参数范围,减少参数协商失败的概率。
4.5 本章小结
- PCM 设备注册遵循 分配 → 设置 ops → 设置约束 → 注册 的标准流程。
- Playback 和 Capture 流是独立的子流,拥有各自的 DMA 缓冲区和硬件参数。
hw_params回调是参数协商的核心,负责验证、配置和资源分配。- 硬件约束机制帮助用户空间快速找到支持的参数组合,提升兼容性。