3. ALSA 核心数据结构:驱动骨架的基石
在 ALSA 驱动框架中,核心数据结构是连接硬件、音频流与用户空间的桥梁。理解 snd_card、snd_pcm、snd_pcm_substream、snd_pcm_ops、snd_soc_dai、snd_soc_codec 和 snd_soc_platform 的职责与关系,是编写音频驱动的第一步。这些结构体共同构成了 ASoC(ALSA System on Chip)分层架构的骨骼。
3.1 顶层容器:snd_card
snd_card 是 ALSA 驱动的顶层管理结构,代表一个物理声卡。每个声卡在系统中对应一个 cardX 设备节点(如 card0)。驱动初始化时,必须首先创建并注册 snd_card。
- 职责:管理声卡下的所有子设备(PCM、Control、MIDI 等),提供设备索引、ID 字符串、内存分配与释放接口。
- 关键函数:
snd_card_new()创建,snd_card_register()注册,snd_card_free()释放。 - 生命周期:驱动加载时创建,卸载时释放;一个
snd_card可包含多个snd_pcm实例。
snd_card_new() 的第四个参数 size 用于在 card 结构体尾部附加私有数据,常用于存放硬件资源句柄(如寄存器基址、中断号),避免全局变量。
3.2 PCM 设备核心:snd_pcm
snd_pcm 代表一个 PCM 设备实例,对应 /dev/snd/pcmC0D0p 或 /dev/snd/pcmC0D0c 等设备文件。它封装了 PCM 流的配置参数(采样率、格式、通道数)和运行时状态。
| 字段/概念 | 说明 |
|---|---|
streams[2] | 包含 playback 和 capture 两个方向的 snd_pcm_str 结构 |
name | 设备名称,如 "AC97 PCM" |
card | 指向所属 snd_card 的指针 |
private_data | 驱动私有数据,通常指向硬件描述结构 |
ops | 指向 snd_pcm_ops 操作函数集 |
3.3 流实例:snd_pcm_substream
snd_pcm_substream 是 PCM 数据流的运行时实例。当应用程序打开 PCM 设备时,内核会分配一个 substream。它包含了当前流的硬件参数、软件参数、DMA 缓冲区指针、运行时状态等。
- 关键字段:
runtime(snd_pcm_runtime结构,包含 hw_params、sw_params、DMA 地址等)、pcm(所属snd_pcm)、number(子流编号)。 - 方向:每个 substream 只属于 playback 或 capture 之一,由
stream字段标识。 - 多实例:一个
snd_pcm可支持多个 substream(如多声道录音),但通常嵌入式设备只使用一个 playback 和一个 capture substream。
snd_pcm_substream 是驱动与应用程序交互的直接对象。中断处理函数、DMA 完成回调、指针更新都通过 substream 访问运行时数据。
3.4 操作函数集:snd_pcm_ops
snd_pcm_ops 是驱动必须实现的操作函数表,定义了 PCM 设备的标准行为。ALSA 核心在用户调用 ioctl 时,会回调这些函数。
| 函数指针 | 触发时机 | 典型实现 |
|---|---|---|
open | 设备打开时 | 初始化硬件、分配 DMA 缓冲区 |
close | 设备关闭时 | 释放资源、停止 DMA |
ioctl | 特殊控制命令 | 处理非标准硬件控制 |
hw_params | 设置硬件参数时 | 配置采样率、格式、DMA 通道 |
hw_free | 释放硬件参数时 | 释放 DMA 缓冲区 |
prepare | 准备播放/录音时 | 重置 DMA 指针、配置硬件 FIFO |
trigger | 启动/停止/暂停时 | 操作 DMA 引擎启停 |
pointer | 查询当前 DMA 位置时 | 返回硬件指针(DMA 传输进度) |
copy/silence | 数据拷贝/静音填充 | 用于非 MMAP 模式或特殊硬件 |
3.5 ASoC 分层结构:DAI、Codec、Platform
ASoC 将嵌入式音频驱动分为三个独立组件,通过标准接口组合,提高代码复用性。
3.5.1 snd_soc_dai:数字音频接口
snd_soc_dai 代表 CPU 侧的 DAI 控制器(如 I2S、PCM 接口)。它负责时钟生成、帧同步、数据移位等数字音频协议。
- 关键字段:
name(如 "i2s-hifi")、playback/capture(snd_soc_pcm_stream结构,描述支持的格式)、ops(snd_soc_dai_ops函数集)。 - 驱动职责:实现
hw_params、trigger、set_fmt等回调,配置 I2S 时钟极性、位宽、主从模式。
3.5.2 snd_soc_codec:音频编解码器
snd_soc_codec 代表外部或内部的音频 Codec 芯片(如 WM8960、ES8316)。它负责 DAC/ADC 转换、音量控制、混音器、输入输出选择。
- 关键字段:
name、regmap(寄存器读写接口)、controls(kcontrol 列表)、dapm(动态音频电源管理)。 - 驱动职责:实现 Codec 寄存器初始化、采样率配置、电源管理序列。
3.5.3 snd_soc_platform:DMA 引擎抽象
snd_soc_platform 代表 SoC 侧的 DMA 引擎或音频接口控制器。它负责将音频数据从内存搬运到 DAI 或从 DAI 搬运到内存。
- 关键字段:
name、pcm_ops(snd_pcm_ops实现)、component(通用组件层)。 - 驱动职责:实现 DMA 通道申请、描述符配置、中断处理、指针更新。
snd_soc_component 统一,但核心概念不变。编写驱动时请确认内核版本对应的 API。
3.6 核心数据结构关系图
下图展示了 ALSA 核心数据结构在 ASoC 框架中的组织关系,从顶层 snd_card 到底层硬件组件:
3.7 数据结构初始化流程示例
以下代码片段展示了在 ASoC 驱动中如何创建并关联这些核心数据结构:
/* 1. 创建 snd_card */
struct snd_card *card;
int ret = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, sizeof(struct my_private_data), &card);
/* 2. 创建 snd_pcm 设备 */
struct snd_pcm *pcm;
ret = snd_pcm_new(card, "my-audio", 0, 1, 1, &pcm);
pcm->private_data = priv;
/* 3. 设置 snd_pcm_ops */
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,
};
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &my_pcm_ops);
/* 4. 注册声卡 */
ret = snd_card_register(card);
3.8 数据结构对比总结表
| 数据结构 | 所属层次 | 核心职责 | 典型数量 |
|---|---|---|---|
snd_card | 顶层 | 声卡管理、设备注册 | 1 个/声卡 |
snd_pcm | PCM 层 | PCM 设备实例、流方向管理 | 1~N 个/声卡 |
snd_pcm_substream | 流层 | 运行时状态、DMA 缓冲区 | 1~N 个/PCM 设备 |
snd_pcm_ops | 操作层 | 硬件控制回调函数集 | 1 套/PCM 设备 |
snd_soc_dai | ASoC 组件 | 数字音频接口控制 | 1~N 个/平台 |
snd_soc_codec | ASoC 组件 | 音频编解码器控制 | 1~N 个/平台 |
snd_soc_platform | ASoC 组件 | DMA 传输与数据搬运 | 1 个/平台 |
snd_card → snd_pcm → snd_pcm_ops 这条主线,再深入 ASoC 的三个组件。理解每个结构体的生命周期和回调时机,是调试音频驱动的基础。