3. ALSA 核心数据结构:snd_card、snd_pcm、snd_control、snd_rawmidi 结构体解析

ALSA 驱动层的核心围绕四个关键数据结构展开:snd_card(声卡实例)、snd_pcm(PCM 设备)、snd_control(控制接口)和 snd_rawmidi(MIDI 设备)。理解这些结构体的层次关系与内部字段,是编写正确 ALSA 驱动的第一步。

核心原则:snd_card 是所有子设备的容器;snd_pcm 负责音频数据流;snd_control 提供用户空间调节接口;snd_rawmidi 处理 MIDI 事件。四者通过链表和指针构成树状拓扑。

3.1 snd_card:声卡实例的根容器

snd_card 是 ALSA 驱动中最高层级的抽象,代表一个物理或逻辑声卡。每个声卡驱动在 probe 阶段必须首先创建并注册一个 snd_card 实例。

snd_card 结构体核心字段与子设备关系 snd_card 关键字段 int number char id[32] char driver[16] char shortname[32] char longname[80] struct list_head devices 子设备链表 ▶ snd_pcm (playback) ▶ snd_pcm (capture) ▶ snd_control ▶ snd_rawmidi ▶ snd_timer ▶ snd_hwdep
驱动开发提示:snd_card 的 number 字段对应 /dev/snd/controlC0 中的数字索引。id 字段用于 alsamixer 等工具识别声卡,建议使用简短且唯一的字符串。

3.2 snd_pcm:音频数据流的核心引擎

snd_pcm 是 ALSA 中最重要的子设备,负责管理音频数据的播放(playback)和录音(capture)。每个 snd_pcm 实例包含两个独立的 substream 链表:一个用于 playback,一个用于 capture。

字段/成员 类型 说明
card struct snd_card * 所属声卡指针
device int 设备索引号(对应 hw:x,y 中的 y)
streams[2] struct snd_pcm_str 0=playback, 1=capture
substream struct snd_pcm_substream * 当前活跃的 substream
ops struct snd_pcm_ops * 硬件操作回调函数集
private_data void * 驱动私有数据指针
name char [32] 设备名称标识
snd_pcm 与 substream 层级关系 snd_pcm streams[0] (playback) streams[1] (capture) substream #0 substream #1 substream #0 substream #1
常见错误:驱动开发新手常混淆 snd_pcm 与 snd_pcm_substream。snd_pcm 是逻辑设备,substream 才是真正处理音频数据的实体。多声道声卡通常一个 substream 对应一个声道对。

3.3 snd_control:用户空间的调节旋钮

snd_control 是 ALSA 控制接口的内核表示,用户通过 alsamixer、amixer 或 alsactl 操作的就是这些 control 实例。每个 control 可以是一个音量滑块、开关或枚举选择器。

struct snd_kcontrol {
    struct list_head list;           // 挂入 snd_card 的 controls 链表
    struct snd_ctl_elem_id id;       // 控制元素标识(name, index, numid)
    unsigned int count;              // 该控制器的元素个数
    struct snd_kcontrol_volatile volatile[SNDRV_CTL_ELEM_ID_MAX_ELEMS];
    struct snd_kcontrol_ops *ops;    // 回调函数:info, get, put, tlv
    void *private_data;              // 驱动私有数据
    unsigned int private_value;      // 常用整型私有值
};
回调函数 触发时机 典型实现
info 用户查询控制信息 填充范围、类型、访问权限
get 用户读取当前值 从硬件寄存器读取音量值
put 用户写入新值 写入硬件寄存器并更新缓存
tlv 用户查询 TLV 数据 返回 dB 刻度映射表
snd_control 注册与用户空间交互流程 用户空间 (amixer) ALSA 控制核心 驱动 snd_kcontrol 回调函数调用链 1. amixer cget numid=3 → info() 返回控制属性 2. amixer cset numid=3 80% → put() 写入硬件 3. amixer cget numid=3 → get() 读取当前值验证
调试技巧:使用 amixer controls 列出所有 control,amixer cget numid=N 查看单个 control 的当前值和属性。驱动中建议为每个硬件可调参数创建独立的 snd_kcontrol 实例。

3.4 snd_rawmidi:MIDI 设备的抽象

snd_rawmidi 结构体用于管理 MIDI 输入/输出设备。与 snd_pcm 类似,它也包含独立的 substream 用于输入和输出方向。

struct snd_rawmidi {
    struct snd_card *card;           // 所属声卡
    struct list_head list;           // 挂入 snd_card 的 rawmidi 链表
    unsigned int device;             // 设备索引
    unsigned int info_flags;         // 设备能力标志
    struct snd_rawmidi_ops *ops;     // 硬件操作回调
    struct snd_rawmidi_substream *substreams[2]; // 0=输出, 1=输入
    void *private_data;
    char name[32];
};
方向 substream 索引 设备节点 典型用途
输出 (Output) 0 /dev/snd/midiC0D0 发送 MIDI 事件到外部设备
输入 (Input) 1 /dev/snd/midiC0D1 接收来自 MIDI 键盘的事件
snd_rawmidi 双方向数据流 MIDI 键盘/合成器 snd_rawmidi sequencer / aplaymidi 输入 (接收) 输出 (发送) 数据流方向:输入 substream 接收硬件 MIDI 事件,输出 substream 发送事件到硬件 每个方向可包含多个 substream,支持多端口 MIDI 设备
驱动实现要点:snd_rawmidi 的 ops 回调包括 open、close、trigger 和 drain。trigger 回调在启动/停止 MIDI 传输时被调用,drain 用于等待输出缓冲区排空。对于 USB MIDI 设备,通常还需要处理 4 字节的 USB MIDI 事件包与标准 MIDI 字节流之间的转换。

3.5 四种核心结构体的对比总结

结构体 创建函数 注册函数 用户空间节点 主要回调集
snd_card snd_card_new() snd_card_register() /dev/snd/controlC0 —(容器角色)
snd_pcm snd_pcm_new() snd_pcm_add() /dev/snd/pcmC0D0p/c open/close/ioctl/hw_params/trigger/pointer
snd_control snd_ctl_new1() snd_ctl_add() 通过 controlC0 访问 info/get/put/tlv
snd_rawmidi snd_rawmidi_new() snd_rawmidi_add() /dev/snd/midiC0D0 open/close/trigger/drain
生命周期管理:所有子设备(snd_pcm、snd_control、snd_rawmidi)的生命周期由 snd_card 管理。驱动卸载时只需调用 snd_card_free(),ALSA 核心会自动释放所有已注册的子设备。切勿手动释放子设备内存,否则会导致双重释放。