3. ALSA 架构核心对象
ALSA(Advanced Linux Sound Architecture)在驱动层抽象出若干核心对象,它们共同构成了声卡驱动的骨架。理解这些对象的职责、生命周期与关联关系,是编写正确、可维护的 ALSA 驱动的第一步。本章将逐一剖析 snd_card、snd_device、snd_pcm、snd_control 和 snd_rawmidi 这五个核心对象。
3.1 核心对象全景图
下面的 SVG 展示了 ALSA 驱动中核心对象的层次结构与关联关系。一个 snd_card 作为顶层容器,内部通过 snd_device 链表管理多个子组件,其中 snd_pcm、snd_control、snd_rawmidi 是最主要的三种功能组件。
3.2 snd_card:声卡实例的根容器
snd_card 是 ALSA 驱动中最高层级的抽象,代表一个物理或逻辑声卡。每个声卡驱动在 probe 阶段必须首先创建并初始化一个 snd_card 实例。
3.2.1 核心职责
- 生命周期管理:作为整个声卡实例的根对象,负责协调所有子组件的创建、注册与销毁。
- 资源容器:持有声卡编号(card number)、设备结构体指针、组件链表头等全局信息。
- 用户空间接口:通过
/dev/snd/controlC0等设备文件与用户态通信。
3.2.2 关键 API
| 函数 | 作用 | 调用时机 |
|---|---|---|
snd_card_new() |
分配并初始化 snd_card 结构体 |
probe 函数最开头 |
snd_card_register() |
向 ALSA 核心注册声卡,创建设备节点 | 所有子组件创建完成后 |
snd_card_free() |
释放声卡及其所有子组件资源 | remove 函数或错误处理路径 |
snd_card_disconnect() |
断开声卡与用户空间的连接(热拔插场景) | 设备意外移除时 |
snd_card_new(),然后依次创建 PCM、Control、MIDI 等子组件,最后调用 snd_card_register()。这种“先构造、后注册”的模式可以避免用户空间在组件未就绪时访问设备。
3.3 snd_device:统一的设备管理链表
snd_device 是 ALSA 内部用于统一管理各种子组件的链表节点。它本身不直接暴露给用户空间,而是作为驱动开发者的内部基础设施。
3.3.1 数据结构
struct snd_device {
struct list_head list; // 链表节点,挂入 snd_card->devices
struct snd_card *card; // 所属声卡
snd_device_type_t type; // 设备类型(PCM/Control/MIDI/...)
void *device_data; // 指向具体子组件实例的指针
struct snd_device_ops *ops; // 操作函数集(dev_free/dev_register/dev_disconnect)
};
3.3.2 设备类型枚举
| 类型宏 | 对应组件 | 说明 |
|---|---|---|
SNDRV_DEV_PCM |
snd_pcm | PCM 播放/录音设备 |
SNDRV_DEV_CONTROL |
snd_control | 混音器与控件接口 |
SNDRV_DEV_RAWMIDI |
snd_rawmidi | 原始 MIDI 设备 |
SNDRV_DEV_TIMER |
snd_timer | 定时器设备 |
SNDRV_DEV_INFO |
snd_info_entry | procfs 信息节点 |
SNDRV_DEV_JACK |
snd_jack | 插拔检测 |
snd_device 链表使得 ALSA 核心可以在 snd_card_free() 时遍历链表,按注册顺序的逆序自动调用每个组件的 dev_free 回调,从而避免资源泄漏。驱动开发者通常不需要直接操作 snd_device,而是通过 snd_pcm_new()、snd_ctl_create() 等封装函数间接使用。
3.4 snd_pcm:PCM 数据流引擎
snd_pcm 是 ALSA 驱动中最核心、最复杂的对象,负责管理音频数据的数字采样流(Pulse Code Modulation)。它直接面向音频数据的传输,是驱动开发中工作量最大的部分。
3.4.1 层次结构
一个 snd_pcm 实例内部包含两个关键子结构:
- snd_pcm_substream:代表一个单向数据流(播放或录音)。一个 PCM 设备通常包含两个 substream(一个播放、一个录音),也可能有多个。
- snd_pcm_ops:操作函数集,驱动开发者需要实现
open、close、hw_params、prepare、trigger、pointer等回调。
3.4.2 核心回调函数
| 回调 | 触发时机 | 驱动职责 |
|---|---|---|
open |
用户打开 PCM 设备时 | 初始化硬件、分配运行时数据结构 |
hw_params |
用户设置采样参数时 | 验证并配置采样率、格式、通道数、DMA 缓冲区 |
prepare |
每次启动传输前 | 重置 DMA 指针、清空 FIFO、准备硬件 |
trigger |
启动/停止/暂停时 | 操作硬件寄存器开始或停止 DMA 传输 |
pointer |
周期性查询当前位置 | 返回当前 DMA 传输的硬件指针位置 |
3.4.3 创建流程
// 1. 创建 PCM 设备实例
struct snd_pcm *pcm;
snd_pcm_new(card, "My PCM", 0, 1, 1, &pcm);
// 参数:card, 名称, 设备编号, 播放流数, 录音流数, 输出指针
// 2. 设置操作函数集
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. 设置硬件描述信息
snd_pcm_hardware my_hw = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.channels_min = 2,
.channels_max = 2,
};
// 在 open 回调中赋值给 runtime->hw
3.5 snd_control:混音与控制接口
snd_control 提供了用户空间访问硬件控制能力的通道,例如音量调节、静音开关、输入源选择、均衡器设置等。它通过 /dev/snd/controlC0 设备文件与用户态交互。
3.5.1 控件(kcontrol)模型
每个控制功能被抽象为一个 snd_kcontrol 实例,包含:
- ID 信息:名称(如 "Master Volume")、索引、类型(布尔/整数/枚举)。
- 值访问回调:
get回调读取当前值,put回调写入新值。 - 信息回调:
info回调返回控件的取值范围、枚举列表等元数据。
3.5.2 常用控件类型
| 类型 | 典型用途 | 值范围 |
|---|---|---|
SNDRV_CTL_ELEM_TYPE_BOOLEAN |
静音开关、通道切换 | 0 或 1 |
SNDRV_CTL_ELEM_TYPE_INTEGER |
音量、增益、均衡器 | 自定义 min/max/step |
SNDRV_CTL_ELEM_TYPE_ENUMERATED |
输入源选择、输出路由 | 枚举字符串列表 |
SNDRV_CTL_ELEM_TYPE_BYTES |
私有数据、效果器参数 | 字节数组 |
SNDRV_CTL_ELEM_TYPE_IEC958 |
S/PDIF 数字音频配置 | IEC958 结构体 |
3.5.3 创建控件示例
static int my_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo) {
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2; // 左右声道
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int my_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) {
ucontrol->value.integer.value[0] = 80; // 左声道
ucontrol->value.integer.value[1] = 80; // 右声道
return 0;
}
static int my_ctl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) {
int left = ucontrol->value.integer.value[0];
int right = ucontrol->value.integer.value[1];
// 写入硬件寄存器
return 1; // 返回值 1 表示值已改变
}
// 注册控件
static struct snd_kcontrol_new my_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "My Volume",
.info = my_ctl_info,
.get = my_ctl_get,
.put = my_ctl_put,
};
snd_ctl_add(card, snd_ctl_new1(&my_control, NULL));
3.6 snd_rawmidi:MIDI 传输通道
snd_rawmidi 提供了原始 MIDI 字节流的传输能力,用于连接 MIDI 键盘、合成器、音序器等设备。它支持双向通信,每个方向独立管理。
3.6.1 核心特性
- 双方向流:每个
snd_rawmidi实例包含输入(接收)和输出(发送)两个 substream。 - 字节流模式:传输的是原始 MIDI 字节(状态字节 + 数据字节),不解析 MIDI 协议。
- 中断驱动:通常基于 UART 或 USB 中断接收/发送数据。
3.6.2 操作函数集
| 回调 | 说明 |
|---|---|
open |
打开 MIDI 子流,初始化硬件 |
close |
关闭 MIDI 子流,释放资源 |
trigger |
启动/停止 MIDI 传输 |
drain |
等待输出缓冲区数据全部发送完成 |
3.6.3 创建示例
struct snd_rawmidi *rmidi;
snd_rawmidi_new(card, "My MIDI", 0, 1, 1, &rmidi);
// 参数:card, 名称, 设备编号, 输出流数, 输入流数, 输出指针
// 设置操作函数集
static struct snd_rawmidi_ops my_midi_output_ops = {
.open = my_midi_open,
.close = my_midi_close,
.trigger = my_midi_output_trigger,
.drain = my_midi_drain,
};
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&my_midi_output_ops);
3.7 对象生命周期与交互流程
下面的 SVG 展示了从驱动加载到声卡注册完成的核心对象创建与交互流程,帮助理解各对象的创建顺序和依赖关系。
3.8 核心对象对比总结
| 对象 | 职责 | 用户空间接口 | 驱动开发工作量 | 关键回调数 |
|---|---|---|---|---|
| snd_card | 声卡容器与生命周期管理 | /dev/snd/controlC* |
低(框架调用) | 0(无回调) |
| snd_device | 内部链表管理,统一注册/释放 | 无直接接口 | 极低(间接使用) | 3(free/register/disconnect) |
| snd_pcm | 音频数据流传输 | /dev/snd/pcmC*D* |
高(核心工作量) | 6+(open/close/hw_params/prepare/trigger/pointer) |
| snd_control | 混音与硬件控制 | /dev/snd/controlC* |
中(控件数量决定) | 3(info/get/put) |
| snd_rawmidi | MIDI 字节流传输 | /dev/snd/midiC*D* |
中低(协议简单) | 4(open/close/trigger/drain) |
snd_card 和 snd_pcm 的关系。一个 snd_card 可以包含多个 snd_pcm 实例(例如 HDMI 音频和模拟音频各一个 PCM 设备),而每个 snd_pcm 又包含多个 substream。务必理清“声卡 → PCM 设备 → 子流”的三层嵌套关系。
3.9 本章小结
- snd_card 是声卡的根容器,负责生命周期管理,通过
snd_card_new()创建、snd_card_register()注册。 - snd_device 是内部链表节点,实现子组件的统一管理,驱动开发者通常不直接操作。
- snd_pcm 是音频数据传输的核心,驱动开发工作量最大,需要实现
hw_params、trigger、pointer等关键回调。 - snd_control 提供混音与控制能力,通过
snd_kcontrol抽象每个控制项,支持布尔、整数、枚举等多种类型。 - snd_rawmidi 处理原始 MIDI 字节流,结构相对简单,适合作为入门 ALSA 驱动的练习对象。