3. ALSA核心数据结构:snd_card、snd_device、snd_pcm、snd_control、snd_rawmidi等核心结构体
ALSA(Advanced Linux Sound Architecture)框架的核心在于其精心设计的对象模型。理解 snd_card、snd_device、snd_pcm、snd_control 和 snd_rawmidi 等核心结构体,是编写声卡驱动的基石。这些结构体不仅定义了硬件资源的抽象,还构成了用户空间与内核空间交互的桥梁。
snd_card(声卡实例),它作为容器管理多个子组件(如 PCM、Control、MIDI)。每个子组件通过 snd_device 注册到 card 上,形成统一的设备树。
3.1 snd_card:声卡实例的根容器
snd_card 是 ALSA 驱动中最高级别的抽象,代表一个物理或逻辑声卡。每个声卡驱动在初始化时,必须首先创建一个 snd_card 实例。它负责管理声卡的全局属性、资源列表以及与用户空间的交互入口。
snd_card 的关键成员包括:
- number:声卡编号(如 card0、card1),由系统分配。
- id:字符串标识符(如 "HDA Intel"),用于用户空间识别。
- driver:驱动名称字符串。
- devices:
struct list_head链表头,挂载所有通过snd_device_new()注册的子设备。 - pcm:
struct list_head链表头,专门挂载 PCM 设备实例。 - controls:
struct list_head链表头,挂载所有控制元素(kcontrol)。 - rawmidis:
struct list_head链表头,挂载 MIDI 设备。
snd_card_new() 创建 card,使用 snd_card_register() 注册到 ALSA 核心。销毁时调用 snd_card_free()。务必在 probe 函数中优先创建 card。
3.2 snd_device:通用设备注册机制
snd_device 是 ALSA 内部用于统一管理各种子设备的抽象层。它不直接暴露给用户空间,而是作为驱动开发者的辅助工具,用于将 PCM、Control、MIDI 等组件注册到 snd_card 上,并统一管理它们的生命周期(初始化、启动、停止、销毁)。
| 成员 | 类型 | 描述 |
|---|---|---|
| card | struct snd_card * |
指向所属的 snd_card 实例 |
| list | struct list_head |
用于挂入 card->devices 链表的节点 |
| type | enum snd_device_type |
设备类型标识(如 SNDRV_DEV_PCM、SNDRV_DEV_CONTROL) |
| ops | struct snd_device_ops * |
操作函数指针集(dev_free、dev_register、dev_disconnect) |
| device_data | void * |
指向具体设备结构体的私有数据指针 |
驱动开发中,通常不直接操作 snd_device,而是通过 snd_pcm_new()、snd_ctl_create() 等高层 API 间接使用。这些 API 内部会调用 snd_device_new() 完成注册。
// snd_device_new 原型
int snd_device_new(struct snd_card *card,
enum snd_device_type type,
void *device_data,
const struct snd_device_ops *ops);
// 典型用法(由 snd_pcm_new 内部调用)
struct snd_pcm *pcm;
snd_pcm_new(card, "My PCM", 0, 1, 0, &pcm);
// 此时 snd_device 已自动创建并挂入 card->devices 链表
3.3 snd_pcm:PCM 音频流的核心
snd_pcm 是 ALSA 框架中最重要的结构体之一,它代表一个 PCM(Pulse Code Modulation)设备实例,负责管理音频数据的播放(playback)和录制(capture)通道。每个 snd_pcm 实例可以包含一个 playback 流和一个 capture 流。
snd_pcm 的关键成员:
- streams[2]:包含两个
snd_pcm_str结构,分别对应 playback(索引0)和 capture(索引1)。每个snd_pcm_str管理一个snd_pcm_substream链表。 - ops:
struct snd_pcm_ops,包含open、close、ioctl、hw_params、hw_free、prepare、trigger、pointer等回调函数。 - private_data:
void *,驱动开发者可以在此存放芯片相关的私有数据结构。 - name:PCM 设备名称(如 "HDA Intel PCM")。
- device:PCM 设备编号(如 0、1、2...)。
snd_pcm_ops 的 pointer 回调中,必须返回当前硬件 DMA 位置(以帧为单位),而不是字节数。返回错误的位置会导致音频卡顿或 XRUN 误报。
3.4 snd_control:混音与音量控制接口
snd_control(实际为 snd_kcontrol)是 ALSA 中用于实现混音器、音量、开关、均衡器等控制功能的抽象。用户空间通过 alsamixer 或 amixer 工具操作的就是这些 control 元素。
每个 snd_kcontrol 实例代表一个控制单元,具有以下核心属性:
| 成员 | 类型 | 描述 |
|---|---|---|
| id | struct snd_ctl_elem_id |
控制元素的唯一标识(numid、name、index、device、subdevice) |
| vd | struct snd_kcontrol_volatile |
易变数据,包括访问权限、事件处理等 |
| count | unsigned int |
该控制包含的值的数量(如立体声为2) |
| private_value | unsigned long |
驱动私有数据,常用于编码硬件寄存器地址或掩码 |
| put | 函数指针 | 写入控制值(如设置音量) |
| get | 函数指针 | 读取当前控制值 |
| info | 函数指针 | 返回控制元素的元信息(类型、取值范围、名称等) |
// 定义一个简单的音量控制
static int my_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct my_chip *chip = snd_kcontrol_chip(kcontrol);
unsigned int left = ucontrol->value.integer.value[0];
unsigned int right = ucontrol->value.integer.value[1];
// 写入硬件寄存器
write_volume_reg(chip, left, right);
return 0;
}
static const 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,
};
3.5 snd_rawmidi:MIDI 设备抽象
snd_rawmidi 是 ALSA 对原始 MIDI(Musical Instrument Digital Interface)设备的抽象。它支持 MIDI 输入和输出,通常用于连接电子乐器、合成器或 MIDI 控制器。
snd_rawmidi 结构体的核心组成:
- name:MIDI 设备名称。
- streams[SNDRV_RAWMIDI_STREAM_LAST+1]:包含 input 和 output 两个方向的数据流管理结构。
- ops:
struct snd_rawmidi_ops,包含open、close、trigger等回调。 - private_data:驱动私有数据指针。
3.6 核心结构体关系总结
下表总结了五个核心结构体的职责与关系:
| 结构体 | 职责 | 用户空间接口 | 生命周期管理 |
|---|---|---|---|
| snd_card | 声卡根容器,管理所有子设备 | /dev/snd/controlC0 | snd_card_new / snd_card_free |
| snd_device | 通用设备注册节点,统一生命周期 | 不直接暴露 | snd_device_new / snd_device_free |
| snd_pcm | 音频播放/录制流管理 | /dev/snd/pcmC0D0p / pcmC0D0c | snd_pcm_new / snd_pcm_free |
| snd_kcontrol | 混音器、音量、开关等控制 | /dev/snd/controlC0 (通过 ioctl) | snd_ctl_add / snd_ctl_remove |
| snd_rawmidi | MIDI 输入/输出设备 | /dev/snd/midiC0D0 | snd_rawmidi_new / snd_rawmidi_free |
在实际驱动开发中,这些结构体通过 snd_device 机制统一挂载到 snd_card 上,形成完整的声卡设备树。理解它们之间的关系,是编写稳定、高效的 ALSA 驱动的第一步。