3. 内核音频驱动框架
Linux ALSA(Advanced Linux Sound Architecture)驱动框架以层次化、面向对象的方式组织音频设备。驱动开发者通过操作四个核心数据结构——snd_card、snd_pcm、snd_control、snd_rawmidi——来完成声卡的探测、注册与数据传输。本章深入剖析这些结构体的职责、关联关系与初始化流程。
核心要点:ALSA 驱动模型遵循“一个 snd_card 对应一个物理声卡,一个 snd_pcm 对应一个音频流方向”的原则。理解这四个结构体的生命周期与互操作关系,是编写稳定音频驱动的基石。
3.1 snd_card:声卡实例的根容器
snd_card 是 ALSA 驱动中最顶层的结构体,代表一个物理声卡或逻辑音频设备。每个声卡驱动在探测阶段必须创建且仅创建一个 snd_card 实例,作为所有子组件的挂载点。
3.1.1 结构体核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
number |
int |
声卡编号,由 ALSA 核心自动分配,对应 /dev/snd/controlC0 中的数字 |
id |
char[16] |
声卡标识字符串,如 "HDA-Intel",用于用户空间识别 |
driver |
char[16] |
驱动名称,如 "hda-intel" |
devices |
struct list_head |
挂载所有子设备(pcm、control、rawmidi 等)的链表头 |
controls_list |
struct list_head |
挂载所有 control 元素的链表 |
private_data |
void * |
驱动私有数据指针,通常指向芯片专有结构体 |
3.1.2 创建与注册流程
- 分配:调用
snd_card_new()分配snd_card实例,传入父设备指针、声卡索引、驱动 ID 等参数。 - 挂载子设备:依次创建 PCM、Control、RawMIDI 等子设备,并通过
snd_device_new()注册到snd_card的devices链表。 - 注册:调用
snd_card_register()将声卡暴露给用户空间,此时/dev/snd/下生成对应的设备节点。 - 释放:在驱动卸载时调用
snd_card_free()递归释放所有子设备与自身内存。
工程经验:在
snd_card_new() 与 snd_card_register() 之间,驱动应完成所有子设备的创建与初始化。一旦注册,用户空间即可访问设备,未就绪的组件可能导致应用程序崩溃。
3.2 snd_pcm:音频数据流的核心引擎
snd_pcm 是 ALSA 驱动中处理音频数据传输的核心结构体。每个 snd_pcm 实例代表一个方向(播放或捕获)的 PCM 设备,驱动通常为每个声卡创建两个 snd_pcm 实例(一个播放、一个捕获)。
3.2.1 结构体层次
snd_pcm 内部包含两个关键子结构体:
snd_pcm_str:表示一个 PCM 流方向(播放或捕获),包含硬件参数约束、运行时状态、DMA 缓冲区描述等。snd_pcm_substream:表示一个 PCM 子流,对应一个打开的文件描述符。一个snd_pcm_str可以包含多个substream,用于多路复用。
3.2.2 关键操作回调
| 回调函数 | 触发时机 | 典型实现 |
|---|---|---|
open |
用户打开 PCM 设备节点时 | 分配 substream 私有数据,初始化硬件指针 |
hw_params |
用户设置采样率、格式、通道数时 | 验证参数合法性,配置 DMA 引擎,分配 DMA 缓冲区 |
prepare |
每次开始传输前 | 重置 DMA 指针,清空 FIFO,配置中断 |
trigger |
开始/停止/暂停传输时 | 启动或停止 DMA 传输,使能/禁用中断 |
pointer |
ALSA 核心轮询当前硬件位置时 | 返回当前 DMA 传输位置(字节偏移) |
常见陷阱:
hw_params 回调中分配的 DMA 缓冲区必须在 hw_free 回调中释放。如果驱动在 hw_params 中申请了内存但未在 hw_free 中释放,会导致内存泄漏。此外,trigger 回调运行在原子上下文中,禁止睡眠或调用可能睡眠的函数。
3.2.3 创建 PCM 实例的代码模板
static int mycard_pcm_new(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
int err;
// 创建 PCM 实例,方向为播放+捕获
err = snd_pcm_new(card, "My PCM", device, 1, 1, &pcm);
if (err < 0)
return err;
// 设置操作回调集
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &my_capture_ops);
// 分配 DMA 缓冲区(预分配)
snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_DEV_SG,
card->dev,
64*1024, // 最大缓冲区大小
64*1024); // 最小缓冲区大小
return 0;
}
3.3 snd_control:混音器与参数控制接口
snd_control 是 ALSA 中用于控制音频参数(音量、静音、采样率选择、路由等)的接口。每个 control 元素(snd_kcontrol)代表一个可读写的控制点,用户空间通过 alsamixer 或 amixer 工具访问。
3.3.1 snd_kcontrol 结构体
| 字段 | 说明 |
|---|---|
id |
控制元素的唯一标识,包含名称、索引、设备号等 |
vd |
值域描述,定义可读/写权限、最小值、最大值、步进 |
get |
回调函数,读取当前控制值 |
put |
回调函数,写入控制值 |
private_value |
驱动私有数据,通常用于存储硬件寄存器地址或掩码 |
3.3.2 创建 control 元素的步骤
- 定义
snd_kcontrol_new模板,填充名称、回调函数、权限标志。 - 调用
snd_ctl_new1()分配snd_kcontrol实例。 - 调用
snd_ctl_add()将 control 元素注册到snd_card的controls_list链表。
static int mycard_add_volume_control(struct snd_card *card)
{
struct snd_kcontrol *kctl;
struct snd_kcontrol_new vol_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = my_volume_info,
.get = my_volume_get,
.put = my_volume_put,
.private_value = (unsigned long)&my_hw_reg,
};
kctl = snd_ctl_new1(&vol_control, card);
if (!kctl)
return -ENOMEM;
return snd_ctl_add(card, kctl);
}
命名规范:Control 元素的名称遵循 ALSA 约定,如
"Master Playback Volume"、"Capture Switch"、"PCM Playback Volume"。用户空间工具依赖名称字符串来识别控制点,命名错误会导致混音器无法正确映射。
3.4 snd_rawmidi:MIDI 设备支持
snd_rawmidi 结构体用于支持 MIDI 输入/输出设备。虽然现代音频驱动中 MIDI 使用率下降,但在专业音频设备和合成器中仍然是必备功能。
3.4.1 核心字段与操作
| 组件 | 说明 |
|---|---|
snd_rawmidi |
顶层结构体,包含输入/输出流列表 |
snd_rawmidi_str |
表示输入或输出方向,包含 substream 链表 |
snd_rawmidi_substream |
表示一个打开的 MIDI 连接,包含缓冲区与回调 |
open/close |
子流打开/关闭回调 |
trigger |
启动/停止 MIDI 传输 |
3.4.2 创建 rawmidi 设备
static int mycard_rawmidi_new(struct snd_card *card, int device)
{
struct snd_rawmidi *rmidi;
int err;
err = snd_rawmidi_new(card, "My MIDI", device,
1, // 输出 substream 数量
1, // 输入 substream 数量
&rmidi);
if (err < 0)
return err;
// 设置操作回调
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &my_midi_output_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &my_midi_input_ops);
rmidi->private_data = my_midi_chip;
return 0;
}
3.5 四者关系总结
四个核心数据结构在 ALSA 驱动中形成树形依赖关系:
- snd_card 是根节点,负责生命周期管理与设备注册。
- snd_pcm 处理音频 PCM 数据流,是驱动中性能最关键的组件。
- snd_control 提供参数控制接口,与 snd_pcm 配合实现音量、路由等控制。
- snd_rawmidi 处理 MIDI 数据,独立于 PCM 数据路径。
驱动开发要点:在 probe 函数中,正确的创建顺序是:snd_card_new → snd_pcm_new → snd_ctl_add(多个)→ snd_rawmidi_new → snd_card_register。任何子设备创建失败都应调用 snd_card_free 回滚,避免留下半初始化的声卡。