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 等核心结构体,是编写声卡驱动的基石。这些结构体不仅定义了硬件资源的抽象,还构成了用户空间与内核空间交互的桥梁。

核心概念: ALSA 采用“自顶向下”的层次化设计。最顶层是 snd_card(声卡实例),它作为容器管理多个子组件(如 PCM、Control、MIDI)。每个子组件通过 snd_device 注册到 card 上,形成统一的设备树。

3.1 snd_card:声卡实例的根容器

snd_card 是 ALSA 驱动中最高级别的抽象,代表一个物理或逻辑声卡。每个声卡驱动在初始化时,必须首先创建一个 snd_card 实例。它负责管理声卡的全局属性、资源列表以及与用户空间的交互入口。

snd_card 结构体核心成员与关系 snd_card number, id, driver devices 链表 snd_device 列表 pcm 链表 snd_pcm 列表 controls 链表 snd_kcontrol 列表 rawmidis 链表 snd_rawmidi 列表 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 结构体内部层次 snd_pcm name, device, info_flags streams[SNDRV_PCM_STREAM_LAST+1] playback / capture ops open, close, ioctl, hw_params... private_data 芯片私有数据结构指针 snd_pcm 通过 streams[] 数组管理 playback 和 capture 两个方向 每个方向包含一个 snd_pcm_substream 实例 ops 提供硬件抽象层回调,private_data 指向芯片驱动私有数据

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:驱动私有数据指针。
MIDI 与 PCM 的区别: MIDI 传输的是事件消息(如音符开/关、控制变化),而非连续的音频采样数据。MIDI 的实时性要求高,但数据量远小于 PCM。

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 驱动的第一步。