3. ALSA 核心数据结构:snd_card、snd_pcm、snd_control、snd_rawmidi、snd_device
ALSA 框架在 Linux 内核中通过一组核心数据结构来抽象和管理音频硬件。理解这些结构是编写音频驱动的基础。本章将深入剖析 snd_card、snd_pcm、snd_control、snd_rawmidi 和 snd_device 这五个关键对象,阐明它们在驱动中的角色、生命周期以及相互之间的关联。
3.1 顶层容器:snd_card
snd_card 是整个声卡的顶层管理结构,代表一个物理或逻辑音频设备。每个声卡驱动在初始化时,必须首先创建并注册一个 snd_card 实例。它负责管理该声卡下的所有子组件(PCM、Control、MIDI 等),并提供与用户空间交互的入口。
- 作为声卡的唯一标识(card number),对应
/dev/snd/controlC0中的索引。 - 维护声卡名称、ID、驱动程序信息等元数据。
- 持有子组件链表,统一管理组件的创建、注册与释放。
- 提供电源管理回调接口(suspend/resume)。
驱动中通常通过 snd_card_new() 创建实例,并在所有子组件初始化完成后调用 snd_card_register() 使其生效。释放时使用 snd_card_free()。
struct snd_card *card;
int err;
err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card);
if (err < 0) {
dev_err(&pdev->dev, "snd_card_new failed: %d\n", err);
return err;
}
/* ... 创建 PCM、Control 等子组件 ... */
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
3.2 音频流核心:snd_pcm
snd_pcm 是 ALSA 中最重要的数据流接口,负责管理音频数据的播放(playback)与录制(capture)。每个 snd_pcm 实例对应一个 PCM 设备文件(如 /dev/snd/pcmC0D0p),并包含两个关键子结构:snd_pcm_substream 和 snd_pcm_ops。
| 子结构 | 作用 |
|---|---|
snd_pcm_substream |
代表一个数据流方向(播放或录制),管理硬件参数(采样率、格式、通道数)、运行时状态(snd_pcm_runtime)以及 DMA 缓冲区。 |
snd_pcm_ops |
驱动必须实现的回调函数集合,包括 open、close、hw_params、prepare、trigger、pointer 等,用于控制硬件数据流。 |
snd_pcm_new() 创建 PCM 实例,然后通过 snd_pcm_set_ops() 绑定操作函数。注意区分 playback 和 capture 的 substream 数量,它们可以不同。
struct snd_pcm *pcm;
err = snd_pcm_new(card, "My PCM", 0, 1, 1, &pcm);
if (err < 0) {
snd_card_free(card);
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);
3.3 控制通路:snd_control
snd_control 是 ALSA 的控制接口,用于调节音量、静音、切换输入源、配置滤波器等非数据流操作。用户空间的 alsamixer 和 amixer 工具就是通过 Control 接口与驱动交互的。
每个控制项由 snd_kcontrol 结构体表示,包含名称、类型(如 SNDRV_CTL_ELEM_TYPE_INTEGER)、取值范围以及 info、get、put 回调函数。驱动通过 snd_ctl_add() 将控制项注册到声卡。
static struct snd_kcontrol_new my_volume_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.info = my_volume_info,
.get = my_volume_get,
.put = my_volume_put,
};
err = snd_ctl_add(card, snd_ctl_new1(&my_volume_control, chip));
if (err < 0)
return err;
3.4 传统 MIDI 接口:snd_rawmidi
snd_rawmidi 用于支持 MIDI 输入/输出设备。它提供原始 MIDI 字节流的传输能力,不解析 MIDI 事件,适合连接合成器、MIDI 键盘等设备。每个 snd_rawmidi 实例包含输入和输出两个子流,分别对应 snd_rawmidi_substream。
驱动通过 snd_rawmidi_new() 创建实例,并实现 snd_rawmidi_ops 中的 open、close、trigger 等回调。数据通过 snd_rawmidi_receive() 和 snd_rawmidi_transmit() 在驱动与 ALSA 中间层之间传递。
snd_usbmidi 驱动,但嵌入式平台上的 UART MIDI 或 FPGA 实现仍常直接使用 snd_rawmidi 接口。
3.5 组件管理基类:snd_device
snd_device 是 ALSA 内部用于统一管理各类子组件的抽象基类。它不直接暴露给用户空间,而是作为链表节点,将 PCM、Control、RawMIDI 等组件串联在 snd_card 的设备链表中。每个组件在创建时,ALSA 核心会自动为其分配一个 snd_device 实例,并注册相应的析构函数。
这种设计使得 snd_card_free() 可以遍历设备链表,按逆序自动释放所有子组件,无需驱动手动管理。驱动开发者通常不需要直接操作 snd_device,但理解其存在有助于把握 ALSA 的组件生命周期。
3.6 核心数据结构关系图
下图展示了五个核心数据结构之间的层次关系与数据流向:
3.7 数据结构对比总结
| 数据结构 | 功能定位 | 用户空间设备节点 | 关键回调/操作 |
|---|---|---|---|
snd_card |
声卡顶层容器 | /dev/snd/controlC0 |
snd_card_new/register/free |
snd_pcm |
音频数据流(播放/录制) | /dev/snd/pcmC0D0p/c |
open/close/hw_params/trigger/pointer |
snd_control |
混音器与设备控制 | 通过 /dev/snd/controlC0 访问 |
info/get/put |
snd_rawmidi |
原始 MIDI 字节流 | /dev/snd/midiC0D0 |
open/close/trigger,receive/transmit |
snd_device |
组件生命周期管理基类 | 无直接设备节点 | 内部链表管理,自动析构 |
snd_card 的创建时机,然后按需添加 PCM 和 Control 组件。对于不支持 MIDI 的简单 Codec,可以省略 snd_rawmidi。所有组件注册完成后,务必调用 snd_card_register() 使设备对用户空间可见。