Skip to content

Latest commit

 

History

History
2036 lines (1665 loc) · 69.4 KB

File metadata and controls

2036 lines (1665 loc) · 69.4 KB

Linux ALSA 音频子系统深度解析

基于 Linux Kernel 源码深度分析 路径:sound/ | include/sound/ 分析版本:Linux 6.x mainline


目录

  1. ALSA 架构层次:从用户态到硬件
  2. snd_card:声卡抽象
  3. PCM 子系统
  4. 环形缓冲区与双指针模型
  5. DMA 传输机制
  6. ALSA Control 接口
  7. ASoC 框架:嵌入式音频
  8. DAPM:动态音频电源管理
  9. MIDI 子系统
  10. 用户空间接口
  11. sound/ 目录主要文件一览
  12. 调试工具与 procfs

1. ALSA 架构层次:从用户态到硬件

1.1 从 OSS 到 ALSA 的演进

Linux 最早的音频接口是 OSS(Open Sound System),它以 /dev/dsp/dev/mixer 等设备文件为界面。OSS 存在几个根本性缺陷:

  • 单一打开:同一时间只允许一个进程使用 /dev/dsp,无法混音。
  • API 贫乏:没有标准的硬件参数协商机制,应用难以获知硬件能力。
  • 无法热插拔:设备号静态分配,USB 声卡等动态设备支持很差。
  • 无法细粒度控制:mixer 接口过于简单,无法表达现代 SoC 复杂的音频路径。

ALSA(Advanced Linux Sound Architecture) 由 Jaroslav Kysela 于 1998 年发起,2.6 内核将其纳入主线。ALSA 解决了上述问题:

  • 引入 card/device/substream 三级结构,支持多声卡并存。
  • 设计基于约束集的硬件参数协商(snd_pcm_hw_params)。
  • 提供 /dev/snd/pcmC*D*p(playback)和 pcmC*D*c(capture)命名规则,支持热插拔。
  • 通过 snd_kcontrol 框架统一 mixer/音量/开关控制。

1.2 完整架构层次图

+===========================================================================+
|                           用  户  空  间                                   |
|                                                                           |
|  +------------------+  +-------------------+  +------------------------+ |
|  |  应用程序        |  |  GStreamer / mpv   |  |  aplay / arecord       | |
|  |  (自行调用 API)  |  |  (GStreamer插件)   |  |  (alsa-utils 工具)     | |
|  +--------+---------+  +---------+---------+  +-----------+------------+ |
|           |                      |                         |             |
|  +--------v----------------------v-------------------------v------------+ |
|  |                    PipeWire  (新一代音频服务器)                       | |
|  |   会话管理 / 策略路由 / 低延迟混音 / 蓝牙 / JACK 兼容                | |
|  +----------------------------------+----------------------------------+ |
|                                     |                                    |
|  +----------------------------------v----------------------------------+ |
|  |                    alsa-lib  (libasound.so)                         | |
|  |   pcm 插件链 / ctl API / 配置解析(.asoundrc / alsa.conf)            | |
|  +----------------------------------+----------------------------------+ |
+========================================|==================================+
                                         | syscall: open/ioctl/mmap/read/write
+========================================v==================================+
|                           内  核  空  间                                   |
|                                                                           |
|  +----------------------------------------------------------------------+ |
|  |                    ALSA Core  (sound/core/)                           | |
|  |  snd_card | snd_device | snd_minor | proc | power management         | |
|  +-------------------------------+--------------------------------------+ |
|                                  |                                        |
|  +-----------+   +-----------+   +-----------+   +-----------+           |
|  | PCM 子系  |   | Control   |   | RawMIDI   |   | Timer     |           |
|  | 统(sound/ |   | 子系统    |   | 子系统    |   | 子系统    |           |
|  | core/pcm) |   |(core/ctl) |   |(core/mid) |   |(core/tim) |           |
|  +-----+-----+   +-----+-----+   +-----+-----+   +-----+-----+           |
|        |               |               |               |                  |
|  +-----v---------------v---------------v---------------v----------------+ |
|  |                    ASoC Framework  (sound/soc/)                      | |
|  |  snd_soc_card | CPU DAI | Codec | Platform | DAPM                   | |
|  +-------------------------------+--------------------------------------+ |
|                                  |                                        |
|  +-------------------------------v--------------------------------------+ |
|  |               低层驱动  (sound/drivers/ + vendor BSP)               | |
|  |  Intel HDA | USB Audio | ARM PL041 | Qualcomm Q6DSP | ...           | |
|  +-------------------------------+--------------------------------------+ |
+========================================|==================================+
                                         | DMA / I2S / AC97 / HDA / USB
+========================================v==================================+
|                              硬  件  层                                   |
|   DAC/ADC Codec芯片  |  I2S总线  |  HDMI音频  |  USB声卡  |  PCIe HDA   |
+===========================================================================+

1.3 各层职责说明

层次 代表组件 主要职责
应用层 GStreamer、mpv、aplay 产生/消费 PCM 数据
音频服务器 PipeWire、PulseAudio 策略路由、混音、低延迟调度
alsa-lib libasound.so 封装 ioctl、实现 PCM 插件链
ALSA Core sound/core/ 声卡抽象、设备注册、proc 文件
PCM/CTL/MIDI 各子系统 数据传输、控制、MIDI 协议
ASoC sound/soc/ 嵌入式 SoC 三件套 + DAPM
低层驱动 HDA、USB Audio 操作具体硬件寄存器、DMA
硬件 Codec、DAC/ADC 模数转换、物理信号

1.4 设备文件命名规则

ALSA 在 /dev/snd/ 下创建如下设备节点,major 号为 116(include/sound/core.h:26):

/dev/snd/
  controlC0          -- 声卡 0 的控制接口
  pcmC0D0p           -- 声卡 0, 设备 0, 播放(p)
  pcmC0D0c           -- 声卡 0, 设备 0, 录音(c)
  pcmC0D1p           -- 声卡 0, 设备 1, 播放
  midiC0D0           -- 声卡 0, MIDI 设备 0
  seq                -- ALSA sequencer
  timer              -- 全局 timer 设备

命名格式:pcmC<card>D<device><direction>,其中 p = playback,c = capture。


2. snd_card:声卡抽象

2.1 snd_card 结构体详解

snd_card 是整个 ALSA 最顶层的对象,代表一块物理(或逻辑)声卡。定义于 include/sound/core.h:80

/* include/sound/core.h:80 */
struct snd_card {
    int number;          /* 声卡编号,对应 snd_cards[] 数组下标 */
    char id[16];         /* 用户可见的 id 字符串,如 "HDA-Intel" */
    char driver[16];     /* 驱动名称 */
    char shortname[32];  /* 短名称 */
    char longname[80];   /* 完整名称,出现在 /proc/asound/cards */
    char mixername[80];  /* mixer 名称 */
    char components[128];/* 组件列表,空格分隔 */
    struct module *module;

    void *private_data;              /* 驱动私有数据 */
    void (*private_free)(struct snd_card *card);
    struct list_head devices;        /* 挂载的 snd_device 链表 */

    struct device *ctl_dev;          /* /dev/snd/controlC* 对应的 device */
    unsigned int last_numid;         /* 最近分配的数字 ID */
    struct rw_semaphore controls_rwsem;
    rwlock_t controls_rwlock;
    int controls_count;
    struct list_head controls;       /* 所有 kcontrol 链表 */
    struct list_head ctl_files;      /* 已打开的 control 文件 */

    struct snd_info_entry *proc_root; /* /proc/asound/cardX/ 根目录 */
    struct proc_dir_entry *proc_root_link;

    spinlock_t files_lock;
    int shutdown;
    struct device *dev;
    struct device card_dev;          /* sysfs cardX 对象 */
    bool registered;
    bool managed;
    int sync_irq;                    /* 用于 PCM sync 的 IRQ */

    size_t total_pcm_alloc_bytes;    /* 已分配的 PCM 缓冲区总量 */
    struct mutex memory_mutex;
    unsigned int power_state;        /* 电源状态 */
};

字段分类说明:

字段组 字段 作用
身份信息 id, shortname, longname, driver 声卡描述,出现在 /proc/asound/cards
设备管理 devices 链表 挂载 PCM/CTL/MIDI/Timer 等 snd_device
控制管理 controls, controls_count, last_numid 管理所有 snd_kcontrol
procfs proc_root, proc_root_link 暴露调试信息到 /proc/asound/
电源 power_state, power_ref, power_sleep 支持 suspend/resume
内存 total_pcm_alloc_bytes, memory_mutex 限制 DMA 缓冲区总量

2.2 snd_device 与设备类型

每个挂载到声卡的子系统(PCM、CTL、MIDI 等)都通过 snd_device 结构体链接(include/sound/core.h:67):

/* include/sound/core.h:38 */
enum snd_device_type {
    SNDRV_DEV_LOWLEVEL,
    SNDRV_DEV_INFO,
    SNDRV_DEV_BUS,
    SNDRV_DEV_CODEC,
    SNDRV_DEV_PCM,
    SNDRV_DEV_COMPRESS,
    SNDRV_DEV_RAWMIDI,
    SNDRV_DEV_TIMER,
    SNDRV_DEV_SEQUENCER,
    SNDRV_DEV_HWDEP,
    SNDRV_DEV_JACK,
    SNDRV_DEV_CONTROL,   /* 必须是最后一个,注册顺序决定 */
};

/* include/sound/core.h:67 */
struct snd_device {
    struct list_head list;          /* 链接到 snd_card.devices */
    struct snd_card *card;
    enum snd_device_state state;
    enum snd_device_type type;
    void *device_data;              /* 指向具体子系统(snd_pcm 等)*/
    const struct snd_device_ops *ops;
};

snd_device_type 的枚举值同时定义了注册顺序,SNDRV_DEV_CONTROL 最后注册,确保控制接口在所有设备就绪后再暴露给用户空间。

2.3 snd_card_new() 初始化流程

snd_card_new(parent_dev, idx, xid, module, extra_size, &card)
    |
    +-- 分配 snd_card + extra_size 内存(kzalloc)
    |
    +-- 初始化各锁:controls_rwsem, controls_rwlock, files_lock
    |
    +-- 分配声卡编号(snd_cards[] 数组,最多 SNDRV_CARDS=8 或动态上限)
    |
    +-- 初始化 card->card_dev(kobject 等)
    |
    +-- snd_info_card_create() -> 创建 /proc/asound/cardX/ 目录
    |
    +-- 返回 card,驱动继续初始化各子设备

关键 API(include/sound/core.h:282):

int snd_card_new(struct device *parent, int idx, const char *xid,
                 struct module *module, int extra_size,
                 struct snd_card **card_ret);

extra_size 允许驱动在 snd_card 末尾额外分配私有数据,通过 card->private_data 访问,避免二次分配。

2.4 snd_card_register() 注册流程

snd_card_register(card)
    |
    +-- 遍历 card->devices 链表,按 snd_device_type 顺序调用 dev->ops->dev_register()
    |       SNDRV_DEV_PCM      -> snd_pcm_dev_register() -> 创建 /dev/snd/pcmC*D*
    |       SNDRV_DEV_RAWMIDI  -> snd_rawmidi_dev_register()
    |       SNDRV_DEV_CONTROL  -> snd_ctl_dev_register() -> 创建 /dev/snd/controlC*
    |
    +-- 在 /proc/asound/cards 中添加条目
    |
    +-- 在 sysfs 中注册 card_dev(/sys/class/sound/cardX)
    |
    +-- card->registered = true

2.5 /proc/asound/ 接口

/proc/asound/ 是 ALSA 最主要的调试入口:

/proc/asound/
  cards           -- 所有声卡列表
  devices         -- 所有设备(pcm/ctl/midi)
  modules         -- 加载的 ALSA 模块
  card0/
    id            -- 声卡 ID 字符串
    pcm0p/        -- PCM 设备 0 播放
      info        -- 硬件信息
      sub0/
        hw_params -- 当前硬件参数
        status    -- 运行时状态(ptr / xrun)
        sw_params -- 软件参数
    pcm0c/        -- PCM 设备 0 录音
    ...

2.6 声卡组件列表

snd_card.components 字段(128 字节,空格分隔)用于描述声卡能力,例如:

"HDA:10EC0269,17AA21FA,00100103 AC97a:AD1981"

ASoC 声卡在探测完成后还会通过 snd_soc_card.components 填充类似信息。


3. PCM 子系统

3.1 PCM 数据结构层次

PCM 子系统有三层核心结构体:

snd_pcm                  -- PCM 设备实体
  |
  +-- snd_pcm_str[2]     -- 方向流(playback=0, capture=1)
        |
        +-- snd_pcm_substream  -- 具体子流(支持多个同方向子流)
              |
              +-- snd_pcm_runtime -- 运行时状态(open 时分配)

3.1.1 snd_pcm

/* include/sound/pcm.h:534 */
struct snd_pcm {
    struct snd_card *card;
    struct list_head list;
    int device;                  /* 设备编号(0-based) */
    unsigned int info_flags;
    char id[64];
    char name[80];
    struct snd_pcm_str streams[2]; /* [0]=playback, [1]=capture */
    struct mutex open_mutex;
    wait_queue_head_t open_wait;
    void *private_data;
    bool internal;               /* 内部使用,不对用户空间暴露 */
    bool nonatomic;              /* 非原子模式(如 USB Audio) */
};

3.1.2 snd_pcm_str

/* include/sound/pcm.h:513 */
struct snd_pcm_str {
    int stream;                  /* SNDRV_PCM_STREAM_PLAYBACK 或 _CAPTURE */
    struct snd_pcm *pcm;
    unsigned int substream_count;
    unsigned int substream_opened;
    struct snd_pcm_substream *substream; /* 子流链表头 */
    struct snd_kcontrol *chmap_kctl;     /* 通道映射控件 */
    struct device *dev;
};

3.1.3 snd_pcm_substream

/* include/sound/pcm.h:464 */
struct snd_pcm_substream {
    struct snd_pcm *pcm;
    struct snd_pcm_str *pstr;
    void *private_data;
    int number;
    char name[32];
    int stream;                        /* 方向 */
    struct pm_qos_request latency_pm_qos_req;
    size_t buffer_bytes_max;           /* 环形缓冲区最大字节数 */
    struct snd_dma_buffer dma_buffer;  /* DMA 缓冲区 */
    size_t dma_max;
    const struct snd_pcm_ops *ops;     /* 驱动操作集 */
    struct snd_pcm_runtime *runtime;   /* 运行时状态,open 后分配 */
    struct snd_timer *timer;
    unsigned timer_running: 1;
    long wait_time;
    struct snd_pcm_substream *next;    /* 同方向下一个子流 */
    struct list_head link_list;
    struct snd_pcm_group self_group;
    struct snd_pcm_group *group;
    int ref_count;
    atomic_t mmap_count;
    unsigned int f_flags;
    void (*pcm_release)(struct snd_pcm_substream *);
    struct pid *pid;
};

3.1.4 snd_pcm_runtime

运行时结构体在 open 时分配,包含完整的状态信息(include/sound/pcm.h:362):

/* include/sound/pcm.h:362 */
struct snd_pcm_runtime {
    /* 状态 */
    snd_pcm_state_t state;
    snd_pcm_state_t suspended_state;
    int overrange;
    snd_pcm_uframes_t avail_max;
    snd_pcm_uframes_t hw_ptr_base;
    snd_pcm_uframes_t hw_ptr_interrupt;
    unsigned long hw_ptr_jiffies;
    snd_pcm_sframes_t delay;

    /* HW 参数 */
    snd_pcm_access_t access;
    snd_pcm_format_t format;
    unsigned int rate;
    unsigned int channels;
    snd_pcm_uframes_t period_size;
    unsigned int periods;
    snd_pcm_uframes_t buffer_size;
    size_t byte_align;
    unsigned int frame_bits;
    unsigned int sample_bits;

    /* SW 参数 */
    snd_pcm_uframes_t start_threshold;
    snd_pcm_uframes_t stop_threshold;
    snd_pcm_uframes_t silence_threshold;
    snd_pcm_uframes_t silence_size;
    snd_pcm_uframes_t boundary;

    /* mmap */
    struct snd_pcm_mmap_status *status;
    struct snd_pcm_mmap_control *control;

    /* 等待队列 */
    snd_pcm_uframes_t twake;
    wait_queue_head_t sleep;
    wait_queue_head_t tsleep;

    /* 硬件描述 */
    struct snd_pcm_hardware hw;
    struct snd_pcm_hw_constraints hw_constraints;

    /* DMA */
    unsigned char *dma_area;       /* 内核虚拟地址 */
    dma_addr_t dma_addr;           /* 物理总线地址 */
    size_t dma_bytes;
    struct snd_dma_buffer *dma_buffer_p;
};

3.2 PCM 状态机

                    open()
                      |
                      v
              +-------+-------+
              |    OPEN       |
              +-------+-------+
                      | hw_params()
                      v
              +-------+-------+
              |    SETUP      |
              +-------+-------+
                      | prepare()
                      v
              +-------+-------+
  xrun -----> |   PREPARED    | <-------- drain done (playback)
              +-------+-------+
                      | trigger(START)
                      v
              +-------+-------+
              |    RUNNING    | <-- period elapsed loop
              +-------+-------+
                   /       \
     trigger(PAUSE)         trigger(STOP)
            /                 \
    +------+------+     +------+------+
    |    PAUSED   |     |    XRUN     |
    +------+------+     +------+------+
           |
    trigger(PAUSE_RELEASE)
           |
           v
        RUNNING

ALSA PCM 状态定义在 include/uapi/linux/soundcard.hinclude/uapi/sound/asound.h 中。驱动通过 snd_pcm_stop_xrun() 将状态推入 XRUN

3.3 snd_pcm_ops:驱动操作集

驱动通过实现 snd_pcm_ops 向 ALSA 核心提供回调(include/sound/pcm.h:55):

/* include/sound/pcm.h:55 */
struct snd_pcm_ops {
    int (*open)(struct snd_pcm_substream *substream);
    int (*close)(struct snd_pcm_substream *substream);
    int (*ioctl)(struct snd_pcm_substream *substream,
                 unsigned int cmd, void *arg);
    int (*hw_params)(struct snd_pcm_substream *substream,
                     struct snd_pcm_hw_params *params);
    int (*hw_free)(struct snd_pcm_substream *substream);
    int (*prepare)(struct snd_pcm_substream *substream);
    int (*trigger)(struct snd_pcm_substream *substream, int cmd);
    int (*sync_stop)(struct snd_pcm_substream *substream);
    snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
    int (*get_time_info)(...);
    int (*fill_silence)(...);
    int (*copy)(...);
    struct page *(*page)(...);
    int (*mmap)(...);
    int (*ack)(struct snd_pcm_substream *substream);
};

各回调调用时机:

回调 触发时机 主要任务
open 用户打开设备文件 分配私有数据,设置 runtime->hw
close 用户关闭设备文件 释放 open 中的资源
hw_params SNDRV_PCM_IOCTL_HW_PARAMS 配置 DMA、时钟、格式
hw_free SNDRV_PCM_IOCTL_HW_FREE 释放 hw_params 中的资源
prepare SNDRV_PCM_IOCTL_PREPARE 复位 DMA 指针,准备开始传输
trigger START/STOP/PAUSE 命令 原子上下文,启停 DMA
sync_stop 等待 DMA 真正停止 非原子,可睡眠
pointer 周期性被核心查询 返回 hw_ptr 当前位置(帧数)
ack 用户更新 appl_ptr 通知驱动更新 FIFO/预取

3.4 硬件参数协商

snd_pcm_hardware 描述驱动支持的范围(include/sound/pcm.h:32):

/* include/sound/pcm.h:32 */
struct snd_pcm_hardware {
    unsigned int info;           /* SNDRV_PCM_INFO_* flags */
    u64 formats;                 /* SNDRV_PCM_FMTBIT_* 位图 */
    u32 subformats;
    unsigned int rates;          /* SNDRV_PCM_RATE_* 位图 */
    unsigned int rate_min;       /* 最小采样率 */
    unsigned int rate_max;       /* 最大采样率 */
    unsigned int channels_min;
    unsigned int channels_max;
    size_t buffer_bytes_max;     /* 环形缓冲区最大字节数 */
    size_t period_bytes_min;
    size_t period_bytes_max;
    unsigned int periods_min;    /* 最少 period 数 */
    unsigned int periods_max;
    size_t fifo_size;            /* 硬件 FIFO 大小 */
};

协商流程使用约束传播算法,由 snd_pcm_hw_constraints 维护参数区间(include/sound/pcm.h:256):

/* include/sound/pcm.h:256 */
struct snd_pcm_hw_constraints {
    struct snd_mask masks[...];      /* 格式、访问模式等离散参数 */
    struct snd_interval intervals[...]; /* 采样率、周期大小等连续区间 */
    unsigned int rules_num;
    struct snd_pcm_hw_rule *rules;   /* 参数间的依赖规则 */
};

协商步骤:

  1. 驱动在 open() 中设置 runtime->hw,并可调用 snd_pcm_hw_constraint_*() 添加额外约束(如对齐要求)。
  2. 用户空间调用 SNDRV_PCM_IOCTL_HW_PARAMS,传入期望参数。
  3. ALSA 核心对每个参数依次调用约束规则,缩小可行域(refinement loop)。
  4. 若所有参数收敛到单点,则参数确定;否则返回 -EINVAL
  5. 调用驱动的 hw_params() 将确定的参数写入硬件。

3.5 period 与 buffer_size 关系

+----------------------------------------------------------+
|                    环 形 缓 冲 区                         |
|  buffer_size = periods * period_size  (单位:帧)          |
|                                                          |
|  |<---- period_size ---->|<---- period_size ---->|...   |
|  +----------+------------+----------+------------+---+  |
|  | period 0 |  period 1  | period 2 |  period 3  |...+  |
|  +----------+------------+----------+------------+---+  |
|                                                          |
|  ^appl_ptr                              ^hw_ptr          |
+----------------------------------------------------------+
  • buffer_size:整个环形缓冲区的帧数(通常是 period_size 的整数倍)。
  • period_size:每次 DMA 中断/wakeup 的帧数。应用每消费/产生 period_size 帧,驱动产生一次中断。
  • periods:缓冲区中 period 的个数,通常为 2-8。

典型配置(44100 Hz,立体声,S16):

period_size  = 1024 帧  = 4096 字节
periods      = 4
buffer_size  = 4096 帧  = 16384 字节
延迟          = 1024/44100 ≈ 23ms

3.6 snd_pcm_period_elapsed() 完成回调

DMA 中断处理函数完成一个 period 传输后,调用(include/sound/pcm.h:1181):

void snd_pcm_period_elapsed(struct snd_pcm_substream *substream);
void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substream);

内部流程:

DMA 中断 (IRQ context)
    |
    v
驱动中断处理函数
    |
    +-- 更新内部计数
    |
    +-- snd_pcm_period_elapsed(substream)
            |
            +-- 调用 substream->ops->pointer() 获取 hw_ptr
            |
            +-- 更新 runtime->hw_ptr_interrupt
            |
            +-- 检查 xrun(appl_ptr 追不上 hw_ptr)
            |
            +-- 唤醒 runtime->sleep 等待队列
            |    (应用的 poll/read/write 在此等待)
            |
            +-- 更新 runtime->status->hw_ptr(mmap 可见)

4. 环形缓冲区与双指针模型

4.1 环形缓冲区布局

ALSA PCM 使用环形缓冲区(ring buffer)实现零拷贝音频传输。缓冲区在物理上是一块连续(或 SG)DMA 内存,逻辑上形成环形:

物理内存(DMA 可访问):
+--------+--------+--------+--------+--------+--------+
| frame0 | frame1 | frame2 | frame3 | frame4 | frame5 |  ...
+--------+--------+--------+--------+--------+--------+
 offset=0                                    offset=N-1

逻辑环形视图(buffer_size=6帧,periods=3,period_size=2帧):
                   hw_ptr=4
                      |
+------+------+------+v-----+------+------+
|  4   |  5   |  0   |  1   |  2   |  3   |  <- 帧编号
+------+------+------+------+------+------+
                             ^
                          appl_ptr=2

可用空间(playback)= appl_ptr - hw_ptr = 2 - 4 + 6 = 4帧(mod 6)
可用空间(capture) = hw_ptr - appl_ptr = 4 - 2 = 2帧

4.2 hw_ptr vs appl_ptr

两个指针均以帧(frame)为单位,存在绕回(wrap-around)处理(include/sound/pcm.h:371-376):

/* include/sound/pcm.h:371 */
snd_pcm_uframes_t hw_ptr_base;      /* 缓冲区重启时的基准位置 */
snd_pcm_uframes_t hw_ptr_interrupt; /* 最近中断时的 hw_ptr */
unsigned long hw_ptr_jiffies;       /* hw_ptr 上次更新的 jiffies */
u64 hw_ptr_wrap;                    /* 因绕回累积的偏移量 */
指针 含义 更新者
hw_ptr 硬件 DMA 当前位置(已传输到哪里) 驱动(pointer() 回调)
appl_ptr 应用程序当前读/写位置 用户空间(write/read syscall)

播放(Playback)

  • 应用写数据 → appl_ptr 前进
  • DMA 从缓冲区读走数据 → hw_ptr 前进
  • 可用空间 = buffer_size - (appl_ptr - hw_ptr)
  • Underrun(xrun):hw_ptr 追上 appl_ptr,数据消费完

录音(Capture)

  • DMA 写入数据 → hw_ptr 前进
  • 应用读走数据 → appl_ptr 前进
  • 可用数据 = hw_ptr - appl_ptr
  • Overrun(xrun):appl_ptr 追不上 hw_ptr,数据被覆盖

4.3 mmap 用户态直接访问

ALSA 支持将 DMA 缓冲区直接 mmap 到用户进程地址空间,实现真正的零拷贝:

用户进程地址空间:
  0x7f000000  +--------+--------+--------+
              | frame0 | frame1 | frame2 | ...   mmap 映射的 DMA 缓冲区
  0x7f001000  +--------+--------+--------+

              (与内核 DMA 缓冲区 runtime->dma_area 指向同一物理页)

  0x7f002000  +----+----+----+----+
              |hw_p|app_|    |    |   mmap 的 status/control 结构体
  0x7f002000  |tr  |ptr |    |    |
              +----+----+----+----+
              ^                   ^
              snd_pcm_mmap_status  snd_pcm_mmap_control

mmap 流程:

  1. 用户 mmap(fd, 0, buffer_size, PROT_RW, MAP_SHARED, 0)
  2. 内核调用 snd_pcm_mmap()snd_pcm_mmap_data()remap_pfn_range()dma_mmap_coherent()
  3. 用户直接读写缓冲区,用 snd_pcm_mmap_control->appl_ptr 通知内核
  4. 无需 read/write 系统调用,延迟极低

4.4 xrun 处理

xrun 是音频传输中的严重事件:

/* include/sound/pcm.h:106 */
#define SNDRV_PCM_POS_XRUN  ((snd_pcm_uframes_t)-1)

pointer() 返回 SNDRV_PCM_POS_XRUN 或核心检测到指针越界时,snd_pcm_stop_xrun() 被调用,将 substream 状态推入 SNDRV_PCM_STATE_XRUN

xrun 检测路径:

snd_pcm_period_elapsed()
    -> snd_pcm_update_hw_ptr0()
        -> 比较 hw_ptr 和 appl_ptr
        -> 若 hw_ptr 超过 appl_ptr + buffer_size (playback xrun)
           或 hw_ptr - appl_ptr > buffer_size (capture xrun)
        -> snd_pcm_stop_xrun()
            -> snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN)
            -> 通知用户空间(POLLPRI / EPOLLPRI)

应用恢复:调用 SNDRV_PCM_IOCTL_PREPARE 重新准备,再 SNDRV_PCM_IOCTL_START 重新开始。

xrun 调试通过 /proc/asound/card0/pcm0p/sub0/xrun_debug 启用详细日志(include/sound/pcm.h:505-507):

/* include/sound/pcm.h:527 */
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
unsigned int xrun_debug;  /* 0=disabled, 1=verbose, 2=stacktrace */
#endif

5. DMA 传输机制

5.1 snd_dma_buffer 结构

DMA 缓冲区的元数据由 snd_dma_buffer 管理(include/sound/memalloc.h:55):

/* include/sound/memalloc.h:55 */
struct snd_dma_buffer {
    struct snd_dma_device dev;  /* 设备类型和 DMA 方向 */
    unsigned char *area;        /* 内核虚拟地址(CPU 访问) */
    dma_addr_t addr;            /* 物理总线地址(设备访问) */
    size_t bytes;               /* 缓冲区字节数 */
    void *private_data;         /* SG 表等扩展信息 */
};

对应关系:

runtime->dma_area  = dmab->area;   /* 内核虚拟地址 */
runtime->dma_addr  = dmab->addr;   /* 设备物理地址 */
runtime->dma_bytes = dmab->bytes;

5.2 DMA 类型

include/sound/memalloc.h:32-50 定义了多种 DMA 内存类型:

#define SNDRV_DMA_TYPE_UNKNOWN    0  /* 未定义 */
#define SNDRV_DMA_TYPE_CONTINUOUS 1  /* 连续内存,无 DMA(如 vmalloc 模拟) */
#define SNDRV_DMA_TYPE_DEV        2  /* 设备连续 DMA 内存(dma_alloc_coherent) */
#define SNDRV_DMA_TYPE_DEV_WC     5  /* Write-Combined 连续内存 */
#define SNDRV_DMA_TYPE_DEV_IRAM   4  /* 片上 SRAM */
#define SNDRV_DMA_TYPE_VMALLOC    7  /* vmalloc 分配,不可 DMA */
#define SNDRV_DMA_TYPE_NONCONTIG  8  /* 非连续 SG 缓冲区 */
#define SNDRV_DMA_TYPE_DEV_SG     3  /* Scatter-Gather DMA 页 */
#define SNDRV_DMA_TYPE_DEV_WC_SG  6  /* WC + SG */

5.3 snd_dma_alloc_pages() 分配流程

主 API(include/sound/memalloc.h:76):

/* include/sound/memalloc.h:76 */
static inline int snd_dma_alloc_pages(int type, struct device *dev,
                                      size_t size, struct snd_dma_buffer *dmab)
{
    return snd_dma_alloc_dir_pages(type, dev, DMA_BIDIRECTIONAL, size, dmab);
}

内部路由:

snd_dma_alloc_dir_pages(type, dev, dir, size, dmab)
    |
    +-- type == SNDRV_DMA_TYPE_DEV
    |       dma_alloc_coherent(dev, size, &dmab->addr, GFP_KERNEL)
    |       -> 分配物理连续、CPU/设备均可缓存一致的内存
    |
    +-- type == SNDRV_DMA_TYPE_DEV_SG
    |       分配 scatter-gather 表
    |       每页独立分配,通过 sg_table 链接
    |       dmab->area = vmap(pages, ...)  (连续虚拟地址)
    |
    +-- type == SNDRV_DMA_TYPE_VMALLOC
    |       vmalloc(size)  (不可 DMA,仅用于软件模拟)
    |
    +-- type == SNDRV_DMA_TYPE_NONCONTIG
            snd_dma_alloc_pages_fallback 或 SG 方式

5.4 Scatter-Gather DMA

当物理内存碎片化无法分配连续大块内存时,使用 SG DMA:

scatter-gather 表(sg_table):
  +-------+    物理地址
  | sg[0] | -> [0x10000000 ~ 0x10000FFF]  (4KB page)
  +-------+
  | sg[1] | -> [0x20003000 ~ 0x20003FFF]  (4KB page)
  +-------+
  | sg[2] | -> [0x30007000 ~ 0x30007FFF]  (4KB page)
  +-------+
     ...

虚拟地址(连续):
  area = vmap(pages, n, VM_MAP, pgprot_noncached(...))
  -> [0xFFFF800010000000 ~ 0xFFFF800010002FFF]  (连续虚拟)

SG 地址查询辅助函数(include/sound/memalloc.h:97):

dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset);
struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset);
unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
                                      unsigned int ofs, unsigned int size);

5.5 与 dmaengine 框架集成

ASoC Platform 驱动通常通过 Linux 通用 dmaengine 框架驱动 DMA 控制器:

ASoC Platform 驱动(如 sound/soc/fsl/imx-pcm-dma.c)
    |
    +-- dma_request_channel(mask, filter, param)   请求 DMA 通道
    |
    +-- dmaengine_prep_dma_cyclic(chan,             准备循环 DMA
    |       runtime->dma_addr,                      DMA 物理地址
    |       runtime->dma_bytes,                     缓冲区大小
    |       runtime->period_size * frame_bytes,     每次传输大小
    |       direction, flags)
    |       -> 返回 dma_async_tx_descriptor
    |
    +-- descriptor->callback = snd_pcm_period_elapsed  注册完成回调
    |
    +-- dmaengine_submit(descriptor)               提交到 DMA 引擎
    |
    +-- dma_async_issue_pending(chan)              触发 DMA 开始

DMA 完成后,snd_pcm_period_elapsed() 被从 DMA 中断上下文调用,核心更新 hw_ptr 并唤醒应用。

5.6 DMA 内存同步

当 DMA 类型需要显式 cache 同步时(include/sound/memalloc.h:90):

void snd_dma_buffer_sync(struct snd_dma_buffer *dmab,
                         enum snd_dma_sync_mode mode);
/* mode: SNDRV_DMA_SYNC_CPU  -> 同步到 CPU(读之前调用)
         SNDRV_DMA_SYNC_DEVICE -> 同步到设备(DMA 前调用) */

6. ALSA Control 接口

6.1 架构概览

用户空间:          /dev/snd/controlC0
                        |
                   ioctl(SNDRV_CTL_IOCTL_*)
                        |
内核:         sound/core/control.c
                        |
              snd_card.controls 链表
                  |           |
            snd_kcontrol  snd_kcontrol   ...
             (音量)         (静音开关)

Control 接口的主要用途:

  • 音量控制(Volume)
  • 静音开关(Mute Switch)
  • 枚举选择(Enum,如输入源)
  • 路由选择(Mux)
  • EQ 参数
  • 音效开关

6.2 snd_kcontrol 结构体

/* include/sound/control.h:70 */
struct snd_kcontrol {
    struct list_head list;      /* 链入 snd_card.controls */
    struct snd_ctl_elem_id id;  /* 控件标识(iface/device/name/index) */
    unsigned int count;         /* 相同元素的数量 */

    snd_kcontrol_info_t *info;  /* 查询控件信息 */
    snd_kcontrol_get_t *get;    /* 读取当前值 */
    snd_kcontrol_put_t *put;    /* 写入新值 */

    union {
        snd_kcontrol_tlv_rw_t *c;  /* 动态 TLV(回调方式) */
        const unsigned int *p;      /* 静态 TLV 数据 */
    } tlv;

    unsigned long private_value;   /* 驱动私有值(常用来编码寄存器地址/位) */
    void *private_data;
    void (*private_free)(struct snd_kcontrol *kcontrol);
    struct snd_kcontrol_volatile vd[] __counted_by(count);
};

三个核心回调函数类型(include/sound/control.h:17-23):

typedef int (snd_kcontrol_info_t)(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_info *uinfo);
typedef int (snd_kcontrol_get_t)(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol);
typedef int (snd_kcontrol_put_t)(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol);

6.3 控件标识:snd_ctl_elem_id

每个控件有唯一的 snd_ctl_elem_id,包含:

/* include/uapi/sound/asound.h */
struct snd_ctl_elem_id {
    unsigned int numid;           /* 数字 ID(内核分配,唯一) */
    snd_ctl_elem_iface_t iface;   /* 接口类型:CARD/HWDEP/MIXER/PCM/RAWMIDI/TIMER/SEQUENCER */
    unsigned int device;          /* 设备编号(用于 PCM/RAWMIDI) */
    unsigned int subdevice;       /* 子设备编号 */
    unsigned char name[44];       /* 人类可读名称(如 "Master Playback Volume") */
    unsigned int index;           /* 同名控件的索引 */
};

ALSA 控件命名规范(alsa-lib 约定):

"Master Playback Volume"     -- 主音量
"Master Playback Switch"     -- 主静音
"PCM Playback Volume"        -- PCM 播放音量
"Capture Volume"             -- 录音音量
"Capture Switch"             -- 录音开关
"Input Source"               -- 输入源选择(枚举)
"Headphone Jack"             -- 耳机插入检测(开关)

6.4 snd_kcontrol_new:控件定义模板

/* include/sound/control.h:47 */
struct snd_kcontrol_new {
    snd_ctl_elem_iface_t iface;
    unsigned int device;
    unsigned int subdevice;
    const char *name;
    unsigned int index;
    unsigned int access;           /* SNDRV_CTL_ELEM_ACCESS_* 权限位 */
    unsigned int count;
    snd_kcontrol_info_t *info;
    snd_kcontrol_get_t *get;
    snd_kcontrol_put_t *put;
    union {
        snd_kcontrol_tlv_rw_t *c;
        const unsigned int *p;
    } tlv;
    unsigned long private_value;
};

典型使用(以 AC97 音量控件为例):

static const struct snd_kcontrol_new my_volume = {
    .iface  = SNDRV_CTL_ELEM_IFACE_MIXER,
    .name   = "Master Playback Volume",
    .info   = snd_ac97_info_volsw,
    .get    = snd_ac97_get_volsw,
    .put    = snd_ac97_put_volsw,
    .private_value = AC97_MASTER | (31 << 8) | (0 << 16),
};

snd_ctl_add(card, snd_ctl_new1(&my_volume, ac97));

6.5 控件管理 API

/* include/sound/control.h:134-144 */
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *kcontrolnew,
                                   void *private_data);
void snd_ctl_free_one(struct snd_kcontrol *kcontrol);
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol);
int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
                    bool add_on_replace);
int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id,
                      struct snd_ctl_elem_id *dst_id);
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid);
struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
                                     const struct snd_ctl_elem_id *id);

控件查找优化:当 CONFIG_SND_CTL_FAST_LOOKUP 开启时,snd_card 内维护两张 XArray 哈希表(include/sound/core.h:107-110):

#ifdef CONFIG_SND_CTL_FAST_LOOKUP
    struct xarray ctl_numids;   /* numid -> kcontrol */
    struct xarray ctl_hash;     /* id hash -> kcontrol */
    bool ctl_hash_collision;
#endif

6.6 TLV:音量曲线

TLV(Tag-Length-Value)用于描述音量与 dB 的映射关系,支持非线性音量曲线。常见 TLV 类型:

TLV 类型 含义
SNDRV_CTL_TLVT_DB_SCALE 线性 dB 刻度(步进固定)
SNDRV_CTL_TLVT_DB_LINEAR 线性增益(非 dB)
SNDRV_CTL_TLVT_DB_RANGE 分段不同步进
SNDRV_CTL_TLVT_DB_MINMAX 最小最大 dB

声卡驱动定义 TLV:

/* 从 -9450 mB 到 0 mB,步进 150 mB(约 1.5 dB) */
static const DECLARE_TLV_DB_SCALE(master_tlv, -9450, 150, 0);

static const struct snd_kcontrol_new my_vol = {
    ...
    .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,
    .tlv.p  = master_tlv,
};

alsa-lib 读取 TLV 后,将寄存器值转换为 dB 显示给用户。PulseAudio/PipeWire 用此实现感知均匀的音量调节。

6.7 控件事件通知

当控件值改变时,驱动应调用(include/sound/control.h:131-132):

void snd_ctl_notify(struct snd_card *card, unsigned int mask,
                    struct snd_ctl_elem_id *id);
void snd_ctl_notify_one(struct snd_card *card, unsigned int mask,
                        struct snd_kcontrol *kctl, unsigned int ioff);

用户空间通过 poll()read() /dev/snd/controlC* 接收事件(SNDRV_CTL_EVENT_MASK_VALUE / SNDRV_CTL_EVENT_MASK_INFO)。


7. ASoC 框架:嵌入式音频

7.1 ASoC 的设计动机

标准 ALSA 驱动将 CPU 侧逻辑、Codec 逻辑和电源管理混在一起,难以复用。ASoC(Audio System on Chip)框架将嵌入式音频系统分解为三个独立可复用的组件:

+------------------------------------------------------------------+
|                      ASoC 三大组件                               |
|                                                                  |
|  +------------------+  +------------------+  +----------------+ |
|  |   CPU DAI        |  |   Codec          |  |   Platform     | |
|  |  (SoC 音频接口) |  |  (编解码芯片)   |  |  (DMA/内存)  | |
|  |                  |  |                  |  |                | |
|  | - I2S/TDM/PDM    |  | - ADC/DAC        |  | - DMA 引擎     | |
|  | - 时钟生成        |  | - 音量控制        |  | - PCM 缓冲区   | |
|  | - BCLK/LRCLK     |  | - 混音器          |  | - Compress     | |
|  +--------+---------+  +--------+---------+  +--------+-------+ |
|           |                      |                    |          |
|           +----------+-----------+--------------------+          |
|                      |                                           |
|              snd_soc_dai_link                                    |
|           (三者的连接描述)                                      |
+------------------------------------------------------------------+
             |
     snd_soc_card(机器驱动,描述整个音频系统)
             |
         snd_card(ALSA 核心声卡对象)

7.2 snd_soc_card 结构体

/* include/sound/soc.h:972 */
struct snd_soc_card {
    const char *name;
    const char *long_name;
    const char *driver_name;
    const char *components;

    struct device *dev;
    struct snd_card *snd_card;      /* 指向 ALSA snd_card */
    struct module *owner;

    struct mutex mutex;
    struct mutex dapm_mutex;

    /* 回调 */
    int (*probe)(struct snd_soc_card *card);
    int (*late_probe)(struct snd_soc_card *card);
    void (*fixup_controls)(struct snd_soc_card *card);
    int (*remove)(struct snd_soc_card *card);
    int (*suspend_pre)(struct snd_soc_card *card);
    int (*suspend_post)(struct snd_soc_card *card);
    int (*resume_pre)(struct snd_soc_card *card);
    int (*resume_post)(struct snd_soc_card *card);
    int (*set_bias_level)(struct snd_soc_card *,
                          struct snd_soc_dapm_context *dapm,
                          enum snd_soc_bias_level level);

    /* DAI 链接 */
    struct snd_soc_dai_link *dai_link;
    int num_links;
    struct list_head rtd_list;
    int num_rtd;

    /* DAPM 图 */
    const struct snd_soc_dapm_widget *dapm_widgets;
    int num_dapm_widgets;
    const struct snd_soc_dapm_route *dapm_routes;
    int num_dapm_routes;
    struct snd_soc_dapm_context *dapm;

    /* 控件 */
    const struct snd_kcontrol_new *controls;
    int num_controls;

    long pmdown_time;
    unsigned int instantiated:1;
    unsigned int fully_routed:1;
    void *drvdata;
};

7.3 DAI Link:三组件的连接

/* include/sound/soc.h 中的 snd_soc_dai_link(约 750 行区域)*/
struct snd_soc_dai_link {
    const char *name;
    const char *stream_name;

    /* CPU / Codec / Platform 组件 */
    struct snd_soc_dai_link_component *cpus;
    unsigned int num_cpus;
    struct snd_soc_dai_link_component *codecs;
    unsigned int num_codecs;
    struct snd_soc_dai_link_component *platforms;
    unsigned int num_platforms;

    int id;   /* 可选的 ID,用于 DPCM */
    const struct snd_soc_ops *ops;
    const struct snd_soc_compr_ops *compr_ops;

    /* 格式协商 */
    unsigned int dai_fmt;    /* SND_SOC_DAIFMT_* */

    /* 特性标志 */
    unsigned int dynamic:1;      /* DPCM 动态链接 */
    unsigned int dpcm_playback:1;
    unsigned int dpcm_capture:1;
    unsigned int ignore_pmdown_time:1;
    unsigned int ignore:1;       /* 后端 BE,不创建 PCM 设备 */
};

使用宏定义 DAI Link(include/sound/soc.h:912):

/* include/sound/soc.h:925 */
SND_SOC_DAILINK_DEFS(primary,
    DAILINK_COMP_ARRAY(COMP_CPU("i2s-controller")),
    DAILINK_COMP_ARRAY(COMP_CODEC("wm8960.1-001a", "wm8960-hifi")),
    DAILINK_COMP_ARRAY(COMP_PLATFORM("imx-pcm-audio")));

static struct snd_soc_dai_link my_card_dai_links[] = {
    {
        .name        = "HiFi",
        .stream_name = "HiFi",
        .dai_fmt     = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
                       | SND_SOC_DAIFMT_CBP_CFP,
        SND_SOC_DAILINK_REG(primary),
    },
};

7.4 snd_soc_pcm_runtime:运行时连接

/* include/sound/soc.h:1143 */
struct snd_soc_pcm_runtime {
    struct device *dev;
    struct snd_soc_card *card;
    struct snd_soc_dai_link *dai_link;
    struct snd_pcm_ops ops;      /* 覆盖 PCM ops(包装驱动 ops) */

    struct snd_pcm *pcm;         /* 关联的 ALSA PCM 设备 */
    struct snd_soc_dai **dais;   /* CPU DAI + Codec DAI 数组 */

    /* 延迟工作(pmdown_time 后关闭电源) */
    struct delayed_work delayed_work;

    /* function mark(防止重入) */
    struct snd_pcm_substream *mark_startup;
    struct snd_pcm_substream *mark_hw_params;
    struct snd_pcm_substream *mark_trigger;

    int num_components;
    struct snd_soc_component *components[] __counted_by(num_components);
};

/* 访问宏:include/sound/soc.h:1196 */
#define snd_soc_rtd_to_cpu(rtd, n)   (rtd)->dais[n]
#define snd_soc_rtd_to_codec(rtd, n) (rtd)->dais[n + (rtd)->dai_link->num_cpus]

7.5 组件注册 API

/* include/sound/soc.h:449 */
int snd_soc_component_initialize(struct snd_soc_component *component,
                                 const struct snd_soc_component_driver *driver,
                                 struct device *dev);
int snd_soc_add_component(struct snd_soc_component *component,
                          struct snd_soc_dai_driver *dai_drv,
                          int num_dai);

组件驱动(snd_soc_component_driver)包含:

struct snd_soc_component_driver {
    const char *name;
    /* PCM 操作 */
    int (*open)(struct snd_soc_component *, struct snd_pcm_substream *);
    int (*close)(struct snd_soc_component *, struct snd_pcm_substream *);
    int (*hw_params)(struct snd_soc_component *, struct snd_pcm_substream *,
                     struct snd_pcm_hw_params *);
    int (*hw_free)(struct snd_soc_component *, struct snd_pcm_substream *);
    int (*prepare)(struct snd_soc_component *, struct snd_pcm_substream *);
    int (*trigger)(struct snd_soc_component *, struct snd_pcm_substream *, int);
    snd_pcm_uframes_t (*pointer)(struct snd_soc_component *,
                                  struct snd_pcm_substream *);
    /* 控件和 DAPM */
    const struct snd_kcontrol_new *controls;
    int num_controls;
    const struct snd_soc_dapm_widget *dapm_widgets;
    int num_dapm_widgets;
    const struct snd_soc_dapm_route *dapm_routes;
    int num_dapm_routes;
    /* 电源管理 */
    int (*suspend)(struct snd_soc_component *);
    int (*resume)(struct snd_soc_component *);
    ...
};

7.6 DAI 格式协商

dai_fmt 字段通过位掩码描述 I2S 总线格式:

SND_SOC_DAIFMT_I2S        标准 I2S(左通道数据在 LRCLK 上升沿后)
SND_SOC_DAIFMT_LEFT_J     Left-Justified
SND_SOC_DAIFMT_RIGHT_J    Right-Justified
SND_SOC_DAIFMT_DSP_A      DSP/PCM 模式 A
SND_SOC_DAIFMT_DSP_B      DSP/PCM 模式 B
SND_SOC_DAIFMT_PDM        脉冲密度调制

SND_SOC_DAIFMT_NB_NF      正常极性(BCLK正常, LRCLK正常)
SND_SOC_DAIFMT_NB_IF      LRCLK 反转
SND_SOC_DAIFMT_IB_NF      BCLK 反转

SND_SOC_DAIFMT_CBP_CFP    Codec 提供 BCLK 和 LRCLK(旧: CODEC_MASTER)
SND_SOC_DAIFMT_CBC_CFC    CPU 提供 BCLK 和 LRCLK(旧: CPU_MASTER)

8. DAPM:动态音频电源管理

8.1 DAPM 设计目标

嵌入式音频系统中,Codec 芯片内部有大量可独立开关的模块(ADC、DAC、PGA、Mixer、PLL 等),这些模块的开关状态直接影响功耗。

DAPM(Dynamic Audio Power Management)的目标:

  • 根据当前激活的音频路径自动计算哪些 widget 需要上电
  • 在路径不再使用时自动下电
  • 所有操作对应用透明,无需手动干预

8.2 DAPM Widget

Widget 是 DAPM 图中的节点,每个 widget 对应 Codec 内部的一个功能模块(include/sound/soc-dapm.h:516):

/* include/sound/soc-dapm.h:516 */
struct snd_soc_dapm_widget {
    enum snd_soc_dapm_type id;  /* widget 类型 */
    const char *name;
    const char *sname;          /* 关联的 PCM 流名称 */
    struct list_head list;
    struct snd_soc_dapm_context *dapm;

    /* 硬件控制 */
    int reg;                    /* 负值 = 无直接寄存器控制 */
    unsigned char shift;
    unsigned int mask;
    unsigned int on_val;
    unsigned int off_val;

    /* 状态标志 */
    unsigned char power:1;
    unsigned char active:1;
    unsigned char connected:1;
    unsigned char force:1;
    unsigned char ignore_suspend:1;
    unsigned char is_supply:1;
    unsigned char is_ep:2;      /* 是否为端点(source/sink) */

    int subseq;                 /* 同类型内的排序 */
    int (*power_check)(struct snd_soc_dapm_widget *w);

    /* 事件回调 */
    unsigned short event_flags;
    int (*event)(struct snd_soc_dapm_widget *, struct snd_kcontrol *, int);

    /* 关联的控件 */
    int num_kcontrols;
    const struct snd_kcontrol_new *kcontrol_news;
    struct snd_kcontrol **kcontrols;

    /* 图连接(边) */
    struct list_head edges[2];  /* edges[0]=inputs, edges[1]=outputs */
    struct list_head work_list;
    struct list_head power_list;
    struct list_head dirty;
    int endpoints[2];           /* 可达端点数 */

    struct clk *clk;
    int channel;
};

Widget 类型(snd_soc_dapm_type):

端点类型:
  snd_soc_dapm_input      外部输入引脚(如 MIC_IN)
  snd_soc_dapm_output     外部输出引脚(如 HP_OUT)
  snd_soc_dapm_mic        麦克风
  snd_soc_dapm_hp         耳机
  snd_soc_dapm_spk        扬声器
  snd_soc_dapm_line       Line 输入/输出

信号处理:
  snd_soc_dapm_pga        可编程增益放大器(PGA)
  snd_soc_dapm_mixer      混音器
  snd_soc_dapm_mux        多路选择器
  snd_soc_dapm_adc        ADC(模数转换)
  snd_soc_dapm_dac        DAC(数模转换)

供电:
  snd_soc_dapm_supply     电源域(如 AVDD、MICBIAS)
  snd_soc_dapm_regulator_supply 调压器
  snd_soc_dapm_clock_supply     时钟源
  snd_soc_dapm_vmid       虚地参考

流域:
  snd_soc_dapm_aif_in     AIF 输入(来自 CPU DAI)
  snd_soc_dapm_aif_out    AIF 输出(到 CPU DAI)

8.3 DAPM Route:路由定义

/* include/sound/soc-dapm.h:473 */
struct snd_soc_dapm_route {
    const char *sink;       /* 目标 widget 名称 */
    const char *control;    /* 控制此路由的 kcontrol 名称(NULL = 总是连通) */
    const char *source;     /* 来源 widget 名称 */

    int (*connected)(struct snd_soc_dapm_widget *source,
                     struct snd_soc_dapm_widget *sink);
    struct snd_soc_dobj dobj;
};

典型路由定义示例(WM8960 Codec):

static const struct snd_soc_dapm_route wm8960_dapm_routes[] = {
    /* ADC 路径:MIC -> PGA -> ADC */
    { "Left Input Mixer", "Boost Switch", "LINPUT1" },
    { "Left Boost Mixer", NULL, "Left Input Mixer" },
    { "Left ADC", NULL, "Left Boost Mixer" },

    /* DAC 路径:DAC -> Mixer -> HP */
    { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
    { "HP_L", NULL, "Left Output PGA" },
    { "Left Output PGA", NULL, "Left Output Mixer" },

    /* 电源路径:MICBIAS 供给所有输入混音器 */
    { "MICB", NULL, "MICBIAS" },
};

路由图可视化(播放路径示例):

  AIF IN (I2S)
      |
      v
  Left DAC  --------+
                    |
              Left Output Mixer  (kcontrol: "PCM Playback Switch")
                    |
              Left Output PGA   (寄存器控制增益)
                    |
                   HP_L  (耳机左声道输出 pin)
                    |
                  [物理引脚 HP_OUT_L]

8.4 DAPM 上下电算法

当用户打开 PCM 设备或调整路由控件时,DAPM 执行路径分析:

触发事件(PCM open / kcontrol change)
    |
    +-- 将受影响的 widget 加入 dirty 列表
    |
    +-- dapm_power_widgets()
            |
            +-- 遍历所有 widget,调用 widget->power_check()
            |       对每个 widget 检查:是否存在从 source 端点
            |       到 sink 端点的活跃路径?
            |
            +-- 计算需要上电和下电的 widget 集合
            |
            +-- 按 subseq 排序(电源域先于信号路径)
            |
            +-- 对需要上电的 widget:
            |       调用 widget->event(SND_SOC_DAPM_PRE_PMU)
            |       写寄存器(reg/shift/on_val)
            |       调用 widget->event(SND_SOC_DAPM_POST_PMU)
            |
            +-- 对需要下电的 widget:
                    调用 widget->event(SND_SOC_DAPM_PRE_PMD)
                    写寄存器(reg/shift/off_val)
                    调用 widget->event(SND_SOC_DAPM_POST_PMD)

事件标志(event_flags):

SND_SOC_DAPM_PRE_PMU   上电前
SND_SOC_DAPM_POST_PMU  上电后
SND_SOC_DAPM_PRE_PMD   下电前
SND_SOC_DAPM_POST_PMD  下电后
SND_SOC_DAPM_PRE_REG   寄存器写前
SND_SOC_DAPM_POST_REG  寄存器写后

8.5 偏置电平(Bias Level)

DAPM 还管理 Codec 的偏置电平(snd_soc_bias_level):

SND_SOC_BIAS_OFF       完全关闭(无电流)
SND_SOC_BIAS_STANDBY   待机(保持充电,快速恢复)
SND_SOC_BIAS_PREPARE   准备中(时钟运行但无信号)
SND_SOC_BIAS_ON        完全开启(信号路径活跃)

snd_soc_card.set_bias_level() 回调允许机器驱动在偏置电平切换时执行额外操作(如控制外部 LDO)。

8.6 pmdown_time 延迟下电

为避免频繁上下电导致的 pop/click 噪声,ASoC 引入 pmdown_time 延迟机制:

/* include/sound/soc.h:1029 */
long pmdown_time;  /* 停止播放后延迟下电的时间(ms,默认 5000ms)*/

PCM 关闭时,通过 delayed_work 延迟 pmdown_time 毫秒后才真正执行 DAPM 下电。


9. MIDI 子系统

9.1 MIDI 协议基础

MIDI(Musical Instrument Digital Interface)是乐器与计算机之间的数字通信协议,传递的不是 PCM 音频数据,而是演奏事件(音符开/关、音量、音色等)。

MIDI 消息格式:

Status byte:  1TTTNNNN
              T = 类型 (0=Note Off, 1=Note On, 2=After Touch, 3=Control, 4=Program, 5=Channel Pressure, 6=Pitch Bend, 7=系统消息)
              N = 通道编号 (0-15)

Data bytes:   0DDDDDDD  (最高位=0, 7位数据)

典型消息:
  Note On:   0x9n  key  velocity   (n=channel)
  Note Off:  0x8n  key  velocity
  Control:   0xBn  ctrl value      (ctrl=7: 音量, ctrl=10: 声像)
  Program:   0xCn  program         (切换音色)
  Pitch:     0xEn  LSB  MSB        (弯音, 14bit)

9.2 snd_rawmidi 结构体

/* include/sound/rawmidi.h */
struct snd_rawmidi_runtime {
    struct snd_rawmidi_substream *substream;
    unsigned int drain: 1;
    unsigned int oss: 1;
    /* MIDI 流缓冲区 */
    unsigned char *buffer;    /* 环形缓冲区 */
    size_t buffer_size;
    size_t appl_ptr;          /* 应用指针 */
    size_t hw_ptr;            /* 硬件指针 */
    size_t avail_min;
    size_t avail;
    size_t xruns;
    size_t align;             /* 对齐(0=字节流, 3=UMP 4字节对齐) */
    wait_queue_head_t sleep;
    void (*event)(struct snd_rawmidi_substream *substream);
    struct work_struct event_work;
};

MIDI 操作集(include/sound/rawmidi.h:39):

/* include/sound/rawmidi.h:39 */
struct snd_rawmidi_ops {
    int (*open)(struct snd_rawmidi_substream *substream);
    int (*close)(struct snd_rawmidi_substream *substream);
    void (*trigger)(struct snd_rawmidi_substream *substream, int up);
    void (*drain)(struct snd_rawmidi_substream *substream);
};

9.3 MIDI 数据流 API

接收(硬件→软件,include/sound/rawmidi.h:166):

int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
                        const unsigned char *buffer, int count);

发送(软件→硬件):

/* include/sound/rawmidi.h:168 */
int snd_rawmidi_transmit_empty(struct snd_rawmidi_substream *substream);
int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream,
                               unsigned char *buffer, int count);
int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count);
int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream,
                          unsigned char *buffer, int count);
int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream);

发送流程(peek+ack 模式,适用于 FIFO 驱动):

驱动发送中断:
    1. snd_rawmidi_transmit_peek(sub, buf, max)   -- 窥视数据,不移动指针
    2. 将 buf 写入硬件 FIFO
    3. snd_rawmidi_transmit_ack(sub, actually_sent) -- 确认已发送字节数
    4. 若缓冲区清空,停止发送中断

接收流程:

硬件接收中断:
    1. 从硬件读取 MIDI 字节
    2. snd_rawmidi_receive(sub, buf, count) -- 写入环形缓冲区
    3. 环形缓冲区满时,xruns++ 并丢弃数据
    4. 唤醒 runtime->sleep(用户 read 等待)

9.4 UMP(Universal MIDI Packet)

Linux 6.x 新增对 MIDI 2.0 UMP 协议的支持,使用 4 字节对齐(align=3)的包格式,通过 snd_ump 设备暴露。


10. 用户空间接口

10.1 /dev/snd/ 设备文件

ALSA 在 /dev/snd/ 下创建多种字符设备:

/dev/snd/controlC<N>        声卡控制接口
/dev/snd/pcmC<N>D<M>p       播放 PCM
/dev/snd/pcmC<N>D<M>c       录音 PCM
/dev/snd/midiC<N>D<M>       RAW MIDI
/dev/snd/seq                 ALSA Sequencer
/dev/snd/timer               全局 Timer

主设备号 116(include/sound/core.h:26),次设备号由 snd_minor 结构体管理(include/sound/core.h:215):

/* include/sound/core.h:215 */
struct snd_minor {
    int type;               /* SNDRV_DEVICE_TYPE_XXX */
    int card;               /* 声卡编号 */
    int device;             /* 设备编号 */
    const struct file_operations *f_ops;
    void *private_data;
    struct device *dev;
    struct snd_card *card_ptr;
};

10.2 PCM ioctl 命令集

用户程序通过 ioctl 与 PCM 设备交互,主要命令(定义于 include/uapi/sound/asound.h):

SNDRV_PCM_IOCTL_PVERSION       -- 获取协议版本
SNDRV_PCM_IOCTL_INFO           -- 获取 PCM 设备信息
SNDRV_PCM_IOCTL_TSTAMP         -- 设置时间戳模式
SNDRV_PCM_IOCTL_TTSTAMP        -- 设置时间戳类型
SNDRV_PCM_IOCTL_HW_REFINE      -- 精化硬件参数(不应用)
SNDRV_PCM_IOCTL_HW_PARAMS      -- 应用硬件参数
SNDRV_PCM_IOCTL_HW_FREE        -- 释放硬件参数
SNDRV_PCM_IOCTL_SW_PARAMS      -- 设置软件参数
SNDRV_PCM_IOCTL_STATUS         -- 获取运行状态(hw_ptr/appl_ptr)
SNDRV_PCM_IOCTL_DELAY          -- 获取延迟帧数
SNDRV_PCM_IOCTL_HWSYNC         -- 同步 hw_ptr
SNDRV_PCM_IOCTL_SYNC_PTR       -- 同步 hw_ptr 和 appl_ptr(mmap 模式)
SNDRV_PCM_IOCTL_PREPARE        -- 准备播放/录音
SNDRV_PCM_IOCTL_RESET          -- 重置指针
SNDRV_PCM_IOCTL_START          -- 开始传输(trigger)
SNDRV_PCM_IOCTL_DROP           -- 立即停止,丢弃剩余数据
SNDRV_PCM_IOCTL_DRAIN          -- 等待播放完成后停止
SNDRV_PCM_IOCTL_PAUSE          -- 暂停/继续
SNDRV_PCM_IOCTL_REWIND         -- 回退 appl_ptr
SNDRV_PCM_IOCTL_FORWARD        -- 前进 appl_ptr
SNDRV_PCM_IOCTL_WRITEI_FRAMES  -- 交织写入 PCM 帧
SNDRV_PCM_IOCTL_READI_FRAMES   -- 交织读取 PCM 帧
SNDRV_PCM_IOCTL_WRITEN_FRAMES  -- 非交织写入
SNDRV_PCM_IOCTL_READN_FRAMES   -- 非交织读取
SNDRV_PCM_IOCTL_LINK           -- 链接多个 substream
SNDRV_PCM_IOCTL_UNLINK         -- 解链

10.3 典型 PCM 播放流程

用户程序:
    fd = open("/dev/snd/pcmC0D0p", O_RDWR)
        |
        v
    ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &hw_params)
        -- 设置 format=S16_LE, channels=2, rate=44100
        -- period_size=1024帧, periods=4
        |
        v
    ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sw_params)
        -- 设置 start_threshold, stop_threshold, avail_min
        |
        v
    mmap(NULL, buffer_size, PROT_RW, MAP_SHARED, fd, 0)
        -- 获得 DMA 缓冲区的虚拟地址(可选)
        |
        v
    ioctl(fd, SNDRV_PCM_IOCTL_PREPARE)
        |
        v
    循环:
        poll(fd, POLLIN, timeout)  -- 等待有空间可写
        写入数据到 mmap 缓冲区(或用 write() 系统调用)
        ioctl(fd, SNDRV_PCM_IOCTL_SYNC_PTR, &sync_ptr)  -- 更新 appl_ptr
        |
        v(首次写入后超过 start_threshold,自动触发)
    ioctl(fd, SNDRV_PCM_IOCTL_DRAIN)  -- 等待播放完成
        |
        v
    close(fd)

10.4 alsa-lib 封装层

alsa-lib(libasound)对上述 ioctl 进行了高层封装:

/* alsa-lib API 示例(非内核代码) */
snd_pcm_t *pcm;
snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);

snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm, params);
snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(pcm, params, 2);
snd_pcm_hw_params_set_rate(pcm, params, 44100, 0);
snd_pcm_hw_params(pcm, params);

snd_pcm_writei(pcm, buf, frames);  /* 阻塞写入 */

alsa-lib 的核心特性:

  1. PCM 插件链plughw(自动格式转换)、dmix(软件混音)、dsnoop(共享录音)、null(丢弃)、file(写文件)等。
  2. 配置文件解析~/.asoundrc/etc/alsa/alsa.conf 定义设备别名和插件链。
  3. 设备命名defaulthw:0,0plughw:0,0pulse(通过 PulseAudio 插件)等。

10.5 PipeWire 取代 PulseAudio 的设计

PipeWire 是下一代 Linux 音频/视频服务器,目标是统一 PulseAudio(桌面音频)和 JACK(专业低延迟音频)的使用场景。

+--------------------------------------------------------+
|                应用程序                                 |
|  PulseAudio APP | JACK APP | GStreamer | Pipewire APP  |
+----+------------+---+------+----+------+----+----------+
     | PA兼容      | JACK兼容  | GStreamer插件 | 原生
     v             v           v              v
+--------------------------------------------------+
|              PipeWire  守护进程                   |
|                                                  |
|  Session Manager (WirePlumber)                   |
|  策略路由 / 设备枚举 / 权限管理                    |
|                                                  |
|  Filter Graph:                                   |
|  source -> resample -> mix -> sink               |
|                                                  |
|  低延迟调度 (SCHED_FIFO / memfd)                 |
+--------------------+-----------------------------+
                     |
          +-----------+----------+
          |                      |
     ALSA (PCM)             Bluetooth (BlueZ)
   /dev/snd/pcmC0D0p         A2DP / HFP

PipeWire 相比 PulseAudio 的优势:

特性 PulseAudio PipeWire
最低延迟 ~50ms <1ms(JACK 兼容模式)
视频支持 支持(V4L2、libcamera)
Flatpak 隔离 有限 完整 portal 支持
Wayland 集成 需适配 原生支持
蓝牙 Codec SBC aptX/LDAC/LC3
配置复杂度 复杂 统一 WirePlumber 脚本

PipeWire 通过 ALSA PCM mmap 模式 与内核交互,使用 POSIX timerfdmemfd(共享内存)在进程间零拷贝传递音频数据。

10.6 ALSA Sequencer

ALSA Sequencer(/dev/snd/seq)提供 MIDI 事件的时序调度和路由,支持:

  • 事件队列(带时间戳的 MIDI 事件)
  • 端口(port)和连接(connection)管理
  • 与 snd_rawmidi 桥接
  • 软件合成器(fluidsynth)连接

11. sound/ 目录主要文件一览

sound/
  core/                    ALSA 核心
    init.c                 -- snd_card_new(), snd_card_register()
    pcm.c                  -- snd_pcm_new(), PCM 设备管理
    pcm_native.c           -- PCM ioctl 处理(hw_params/trigger等)
    pcm_lib.c              -- snd_pcm_period_elapsed(), xrun 处理
    pcm_memory.c           -- DMA 缓冲区分配/释放
    control.c              -- snd_ctl_add(), ioctl 处理
    rawmidi.c              -- MIDI 子系统
    timer.c                -- 全局 timer
    info.c                 -- /proc/asound/ 接口
    sound.c                -- 字符设备注册,major=116
    memalloc.c             -- snd_dma_alloc_pages() 实现

  soc/                     ASoC 框架
    soc-core.c             -- snd_soc_register_card(), 组件管理
    soc-pcm.c              -- ASoC PCM ops 实现
    soc-dapm.c             -- DAPM 图算法
    soc-component.c        -- snd_soc_component 管理
    soc-dai.c              -- DAI 操作封装
    soc-topology.c         -- 固件拓扑加载
    codecs/                -- Codec 驱动(wm8960, rt5651, ...)
    generic/               -- 通用平台驱动

  drivers/                 通用驱动
    dma-engine.c           -- dmaengine 平台驱动
    dummy.c                -- Dummy 声卡(测试用)

  pci/                     PCI 声卡驱动
    hda/                   -- Intel HD Audio
      hda_intel.c          -- HDA 控制器驱动
      hda_codec.c          -- HDA Codec 驱动框架
      patch_realtek.c      -- Realtek Codec 补丁

  usb/                     USB 音频
    card.c                 -- USB Audio Class 驱动
    pcm.c                  -- USB PCM 子系统
    mixer.c                -- USB 混音器

  arm/                     ARM 平台驱动(legacy)
  x86/                     x86 平台特有(HDMI/DP audio)

12. 调试工具与 procfs

12.1 /proc/asound/ 调试接口

# 查看所有声卡
cat /proc/asound/cards
 0 [HDAIntel      ]: HDA-Intel - HDA Intel PCH
                     HDA Intel PCH at 0xdf340000 irq 139

# 查看所有 PCM 设备
cat /proc/asound/devices
  2: [ 0- 0]: digital audio playback
  3: [ 0- 0]: digital audio capture
  4: [ 0- 1]: digital audio playback
  8: [ 0]   : control

# 查看 PCM 状态(播放时)
cat /proc/asound/card0/pcm0p/sub0/status
state: RUNNING
owner_pid   : 1234
trigger_time: 12345678.012345678
tstamp      : 12345678.023456789
delay       : 1024
avail       : 3072
avail_max   : 3072
hw_ptr      : 49152
appl_ptr    : 50176

# 查看当前硬件参数
cat /proc/asound/card0/pcm0p/sub0/hw_params
access: MMAP_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 1024
buffer_size: 4096

# 开启 xrun 调试(stacktrace)
echo 2 > /proc/asound/card0/pcm0p/xrun_debug

12.2 alsa-utils 工具

# 列出所有播放设备
aplay -l

# 测试播放(WAV 文件)
aplay -D hw:0,0 test.wav

# 测试录音
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 out.wav

# 查看/修改混音器
alsamixer             # TUI 界面
amixer set Master 80% # 命令行设置音量
amixer contents       # 列出所有控件

# 显示声卡信息
alsainfo

# MIDI 播放
aplaymidi -p 20:0 test.mid

12.3 内核调试选项

与 ALSA 相关的 Kconfig 调试选项:

CONFIG_SND_DEBUG            -- 开启 ALSA 调试信息
CONFIG_SND_DEBUG_VERBOSE    -- 详细调试输出
CONFIG_SND_PCM_XRUN_DEBUG   -- PCM xrun 调试
CONFIG_SND_CTL_DEBUG        -- Control 接口校验
CONFIG_SND_PCM_OSS          -- OSS 兼容层
CONFIG_SND_DYNAMIC_MINORS   -- 动态次设备号(> 8 块声卡)
CONFIG_SND_MAX_CARDS        -- 最大声卡数(动态模式)

12.4 DebugFS 接口

CONFIG_SND_DEBUG 开启时,/sys/kernel/debug/sound/ 提供额外调试信息:

/sys/kernel/debug/sound/
  card0/
    hda-codec-0/          -- HDA Codec 寄存器 dump
    hda-codec-1/
  seq/                    -- Sequencer 内部状态

12.5 关键调试命令

# 检查 ALSA PCM 子流是否被打开
ls -la /proc/$(pidof pulseaudio)/fd | grep snd

# 通过 strace 观察 ioctl 调用
strace -e ioctl aplay test.wav 2>&1 | grep SNDRV

# 查看 DMA 缓冲区分配情况(总量限制)
cat /proc/asound/card0/pcm0p/sub0/hw_params

# 使用 latency-test 评估 RTT 延迟(JACK 工具)
jack_iodelay

附录 A:关键数据结构速查

snd_card              声卡顶层对象(include/sound/core.h:80)
snd_device            挂载到声卡的子设备(include/sound/core.h:67)
snd_pcm               PCM 设备(include/sound/pcm.h:534)
snd_pcm_str           PCM 方向流(include/sound/pcm.h:513)
snd_pcm_substream     PCM 子流(include/sound/pcm.h:464)
snd_pcm_runtime       PCM 运行时状态(include/sound/pcm.h:362)
snd_pcm_hardware      硬件能力描述(include/sound/pcm.h:32)
snd_pcm_ops           驱动操作集(include/sound/pcm.h:55)
snd_pcm_hw_constraints 参数约束集(include/sound/pcm.h:256)
snd_dma_buffer        DMA 缓冲区元数据(include/sound/memalloc.h:55)
snd_dma_device        DMA 设备描述(include/sound/memalloc.h:22)
snd_kcontrol          控件实例(include/sound/control.h:70)
snd_kcontrol_new      控件模板(include/sound/control.h:47)
snd_ctl_file          已打开的控制文件(include/sound/control.h:105)
snd_soc_card          ASoC 声卡(include/sound/soc.h:972)
snd_soc_dai_link      DAI 连接描述(include/sound/soc.h ~750行区域)
snd_soc_pcm_runtime   ASoC PCM 运行时(include/sound/soc.h:1143)
snd_soc_dapm_widget   DAPM 节点(include/sound/soc-dapm.h:516)
snd_soc_dapm_route    DAPM 路由(include/sound/soc-dapm.h:473)
snd_rawmidi_runtime   MIDI 运行时(include/sound/rawmidi.h:57)
snd_rawmidi_ops       MIDI 操作集(include/sound/rawmidi.h:39)

附录 B:采样格式与位宽

SNDRV_PCM_FMTBIT_S8          8位有符号
SNDRV_PCM_FMTBIT_U8          8位无符号
SNDRV_PCM_FMTBIT_S16_LE      16位有符号小端(CD 标准)
SNDRV_PCM_FMTBIT_S24_3LE     24位有符号小端(3字节紧凑)
SNDRV_PCM_FMTBIT_S24_LE      24位有符号小端(4字节左对齐)
SNDRV_PCM_FMTBIT_S32_LE      32位有符号小端(32位容器,可能24/20有效位)
SNDRV_PCM_FMTBIT_FLOAT_LE    32位浮点小端
SNDRV_PCM_FMTBIT_DSD_U8      DSD(直接流数字)1位PDM,8位打包
SNDRV_PCM_FMTBIT_DSD_U32_LE  DSD 32位打包小端

// include/sound/pcm.h:148-202 中完整定义

每帧(frame)字节数 = channels × sample_bits / 8

例:S16_LE, stereo2 × 16 / 8 = 4 字节/帧

附录 C:常用采样率位图

// include/sound/pcm.h:109-129
SNDRV_PCM_RATE_8000    = (1U<<1)   // 电话
SNDRV_PCM_RATE_16000   = (1U<<3)   // 语音识别
SNDRV_PCM_RATE_44100   = (1U<<6)   // CD 标准
SNDRV_PCM_RATE_48000   = (1U<<7)   // 专业音频/数字广播
SNDRV_PCM_RATE_96000   = (1U<<10)  // Hi-Res Audio
SNDRV_PCM_RATE_192000  = (1U<<12)  // Hi-Res Audio
SNDRV_PCM_RATE_384000  = (1U<<14)  // DSD 等效
SNDRV_PCM_RATE_768000  = (1U<<16)  // DSD512

// 6.12 新增(include/sound/pcm.h:127-129)
SNDRV_PCM_RATE_12000   = (1U<<17)
SNDRV_PCM_RATE_24000   = (1U<<18)
SNDRV_PCM_RATE_128000  = (1U<<19)

由 Claude Code 分析生成