Linux Kernel Sound 子系统深度分析文档
版本: R1 日期: 2026-04-26 源码: Linux Kernel Mainline (master branch)
目录
- ALSA PCM 核心
- PCM 数据传输
- DPCM (Dynamic PCM)
- DAPM (Dynamic Audio Power Management)
- ASoC DAI (Digital Audio Interface)
- 性能优化
1. ALSA PCM 核心
1.1 struct snd_pcm 完整结构
文件: /include/sound/pcm.h:534-554
struct snd_pcm {
struct snd_card *card; // 所属声卡
struct list_head list; // 声卡 PCM 链表节点
int device; // PCM 设备编号
unsigned int info_flags; // 信息标志
unsigned short dev_class; // 设备类
unsigned short dev_subclass; // 设备子类
char id[64]; // 标识符
char name[80]; // 设备名称
struct snd_pcm_str streams[2]; // 播放/录制流 [SNDRV_PCM_STREAM_PLAYBACK/CAPTURE]
struct mutex open_mutex; // 打开互斥锁
wait_queue_head_t open_wait; // 等待队列
void *private_data; // 私有数据
void (*private_free) (struct snd_pcm *pcm); // 私有析构函数
bool internal; // 是否内部使用
bool nonatomic; // 是否非原子操作
bool no_device_suspend; // 是否跳过设备挂起
};关键关系:
snd_pcm
└── streams[2] (playback + capture)
└── snd_pcm_str
└── substreams (子流链表)1.2 struct snd_pcm_runtime 完整字段
文件: /include/sound/pcm.h:362-453
struct snd_pcm_runtime {
/* -- 状态 -- */
snd_pcm_state_t state; // 流状态: OPEN, SETUP, PREPARE, RUNNING, XRUN, DRAINING, SUSPENDED
snd_pcm_state_t suspended_state; // 挂起状态
struct snd_pcm_substream *trigger_master; // 触发主 substream
struct timespec64 trigger_tstamp; // 触发时间戳
bool trigger_tstamp_latched; // 时间戳已锁定
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; // hw_ptr 更新时间
unsigned long hw_ptr_buffer_jiffies; // 缓冲区时间(jiffies)
snd_pcm_sframes_t delay; // 额外延迟(FIFO大小)
u64 hw_ptr_wrap; // hw_ptr 边界环绕偏移
/* -- HW 参数 -- */
snd_pcm_access_t access; // 访问模式: RW_INTERLEAVED, RW_NONINTERLEAVED, MMAP_INTERLEAVED, MMAP_NONINTERLEAVED
snd_pcm_format_t format; // 采样格式: S16_LE, S32_LE, etc.
snd_pcm_subformat_t subformat; // 子格式
unsigned int rate; // 采样率 (Hz)
unsigned int channels; // 声道数
snd_pcm_uframes_t period_size; // 周期大小 (帧)
unsigned int periods; // 周期数
snd_pcm_uframes_t buffer_size; // 缓冲区大小 (帧)
snd_pcm_uframes_t min_align; // 最小对齐
size_t byte_align; // 字节对齐
unsigned int frame_bits; // 每帧比特数
unsigned int sample_bits; // 采样比特数
unsigned int info; // 信息标志
unsigned int rate_num; // 采样率分子
unsigned int rate_den; // 采样率分母
unsigned int no_period_wakeup:1; // 无周期中断唤醒
/* -- SW 参数 -- */
int tstamp_mode; // 时间戳模式
unsigned int period_step; // 周期步进
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; // 指针环绕点
/* -- 静音内部数据 -- */
snd_pcm_uframes_t silence_start; // 静音起始指针
snd_pcm_uframes_t silence_filled; // 已填充静音
bool std_sync_id; // 硬件同步ID
/* -- mmap -- */
struct snd_pcm_mmap_status *status; // 状态 (用户空间只读)
struct snd_pcm_mmap_control *control; // 控制 (用户空间读写)
/* -- 锁定/调度 -- */
snd_pcm_uframes_t twake; // 传输唤醒阈值
wait_queue_head_t sleep; // poll 睡眠队列
wait_queue_head_t tsleep; // 传输睡眠队列
struct snd_fasync *fasync; // 异步通知
bool stop_operating; // 停止运行标志
struct mutex buffer_mutex; // 缓冲区互斥锁
atomic_t buffer_accessing; // 缓冲区访问计数
/* -- 私有 -- */
void *private_data; // 私有数据
void (*private_free)(struct snd_pcm_runtime *runtime); // 析构函数
/* -- 硬件描述 -- */
struct snd_pcm_hardware hw; // 硬件能力
struct snd_pcm_hw_constraints hw_constraints; // 约束规则
/* -- 定时器 -- */
unsigned int timer_resolution; // 定时器分辨率
int tstamp_type; // 时间戳类型
/* -- DMA -- */
unsigned char *dma_area; // DMA 区域(虚拟地址)
dma_addr_t dma_addr; // DMA 物理地址
size_t dma_bytes; // DMA 区域大小
struct snd_dma_buffer *dma_buffer_p; // 分配的缓冲区
unsigned int buffer_changed:1; // 缓冲区已更改
/* -- 音频时间戳配置 -- */
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
struct snd_pcm_audio_tstamp_report audio_tstamp_report;
struct timespec64 driver_tstamp; // 驱动时间戳
};1.3 hw_params vs sw_params 区别
hw_params (硬件参数)
用户空间结构 (/include/uapi/sound/asound.h:408-425):
struct snd_pcm_hw_params {
unsigned int flags; // 标志
struct snd_mask masks[...]; // 格式掩码 (SNDRV_PCM_HW_PARAM_*)
struct snd_interval intervals[...]; // 间隔值 (rate, channels, period_size等)
unsigned int rmask; // 请求的掩码
unsigned int cmask; // 更改的掩码
unsigned int info; // 返回的信息标志
unsigned int msbits; // 有效位
unsigned int rate_num; // 采样率分子
unsigned int rate_den; // 采样率分母
snd_pcm_uframes_t fifo_size; // FIFO 大小
unsigned char sync[16]; // 同步ID
};参数类型 (/include/sound/pcm.h):
SNDRV_PCM_HW_PARAM_ACCESS- 访问模式SNDRV_PCM_HW_PARAM_FORMAT- 采样格式 (S16_LE, S32_LE等)SNDRV_PCM_HW_PARAM_SUBFORMAT- 子格式SNDRV_PCM_HW_PARAM_CHANNELS- 声道数SNDRV_PCM_HW_PARAM_RATE- 采样率SNDRV_PCM_HW_PARAM_PERIOD_SIZE- 周期大小SNDRV_PCM_HW_PARAM_PERIODS- 周期数SNDRV_PCM_HW_PARAM_BUFFER_SIZE- 缓冲区大小SNDRV_PCM_HW_PARAM_BUFFER_BYTES- 缓冲区字节大小SNDRV_PCM_HW_PARAM_ACCESS- 访问模式
sw_params (软件参数)
用户空间结构 (/include/uapi/sound/asound.h:433-448):
struct snd_pcm_sw_params {
int tstamp_mode; // 时间戳模式
unsigned int period_step; // 周期步进
unsigned int sleep_min; // 最小睡眠 ticks
snd_pcm_uframes_t avail_min; // 唤醒所需最小可用帧 (avail_min)
snd_pcm_uframes_t xfer_align; // 传输对齐 (已废弃)
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; // 指针环绕点
};核心区别
| 特性 | hw_params | sw_params |
|---|---|---|
| 用途 | 描述硬件能力/限制 | 描述软件行为策略 |
| 修改频率 | 每次流设置时可能改变 | 可在运行时动态调整 |
| 作用对象 | DMA 缓冲区大小、采样率等 | 启动/停止阈值、唤醒条件 |
| 典型参数 | format, rate, channels | start_threshold, avail_min |
1.4 缓冲区分配 (mmap, rw_ref)
DMA 缓冲区分配流程
文件: /sound/core/pcm_memory.c:420-470
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
{
struct snd_card *card;
struct snd_pcm_runtime *runtime;
struct snd_dma_buffer *dmab = NULL;
// 运行时检查
if (PCM_RUNTIME_CHECK(substream))
return -EINVAL;
if (snd_BUG_ON(substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_UNKNOWN))
return -EINVAL;
runtime = substream->runtime;
card = substream->pcm->card;
// 已有缓冲区且足够大 -> 直接使用
if (runtime->dma_buffer_p) {
if (runtime->dma_buffer_p->bytes >= size) {
runtime->dma_bytes = size;
return 0;
}
snd_pcm_lib_free_pages(substream);
}
// 预分配缓冲区足够 -> 使用预分配
if (substream->dma_buffer.area != NULL &&
substream->dma_buffer.bytes >= size) {
dmab = &substream->dma_buffer;
} else {
// 需要新分配
dmab = kzalloc_obj(*dmab);
if (!dmab)
return -ENOMEM;
dmab->dev = substream->dma_buffer.dev;
if (do_alloc_pages(card,
substream->dma_buffer.dev.type,
substream->dma_buffer.dev.dev,
substream->stream,
size, dmab) < 0) {
kfree(dmab);
return -ENOMEM;
}
}
// 设置运行时缓冲区
snd_pcm_set_runtime_buffer(substream, dmab);
runtime->dma_bytes = size;
return 1;
}预分配缓冲区类型
文件: /include/sound/pcm.h
/* DMA 缓冲区类型 */
enum snd_dma_buffer_type {
SNDRV_DMA_TYPE_UNKNOWN = 0, // 未知类型
SNDRV_DMA_TYPE_CONTINUOUS, // 连续内存 (kmalloc/vmalloc)
SNDRV_DMA_TYPE_VMALLOC, // vmalloc 内存
SNDRV_DMA_TYPE_DEV, // DMA 可访问设备内存
SNDRV_DMA_TYPE_DEV_WC, // Write-Combine 设备内存
SNDRV_DMA_TYPE_EXCLUSIVE, // 独占 DMA 映射
};mmap 机制
文件: /sound/core/pcm_native.c (pcm_mmap)
用户空间通过 mmap() 系统调用将 DMA 缓冲区映射到用户空间,实现零拷贝音频数据传输。
// 关键 mmap 流程:
// 1. snd_pcm_mmap() - 创建 mmap 映射
// 2. snd_pcm_mmap_data() - 映射 PCM 数据缓冲区2. PCM 数据传输
2.1 __snd_pcm_lib_xfer() 详细流程
文件: /sound/core/pcm_lib.c:2271-2416
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
void *data, bool interleaved,
snd_pcm_uframes_t size, bool in_kernel)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t xfer = 0;
snd_pcm_uframes_t offset = 0;
snd_pcm_uframes_t avail;
pcm_copy_f writer; // 传输函数指针
pcm_transfer_f transfer; // 数据拷贝函数
bool nonblock;
bool is_playback;
int err;
// 1. 合理性检查
err = pcm_sanity_check(substream);
if (err < 0)
return err;
is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
// 2. 选择交错式或非交错式传输函数
if (interleaved) {
writer = interleaved_copy;
} else {
writer = noninterleaved_copy;
}
// 3. 选择数据传输函数
if (!data) {
// 静音填充
if (is_playback)
transfer = fill_silence;
else
return -EINVAL;
} else if (substream->ops->copy) {
// 驱动自定义拷贝
transfer = substream->ops->copy;
} else {
// 默认拷贝
transfer = is_playback ? default_write_copy : default_read_copy;
}
if (size == 0)
return 0;
nonblock = !!(substream->f_flags & O_NONBLOCK);
// 4. 获取流锁
snd_pcm_stream_lock_irq(substream);
// 5. 状态检查
err = pcm_accessible_state(runtime);
if (err < 0)
goto _end_unlock;
runtime->twake = runtime->control->avail_min ? : 1;
// 6. 如果正在运行,更新硬件指针
if (runtime->state == SNDRV_PCM_STATE_RUNNING)
snd_pcm_update_hw_ptr(substream);
// 7. 捕获流自动启动
if (!is_playback &&
runtime->state == SNDRV_PCM_STATE_PREPARED &&
size >= runtime->start_threshold) {
err = snd_pcm_start(substream);
if (err < 0)
goto _end_unlock;
}
// 8. 主传输循环
while (size > 0) {
snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
snd_pcm_uframes_t cont;
// 计算可用空间
avail = snd_pcm_avail(substream);
if (!avail) {
//无可用空间,等待
if (nonblock) {
err = -EAGAIN;
goto _end_unlock;
}
// 设置唤醒条件
runtime->twake = min_t(snd_pcm_uframes_t, size,
runtime->control->avail_min ? : 1);
err = wait_for_avail(substream, &avail);
if (err < 0)
goto _end_unlock;
}
// 计算本次传输帧数
frames = size > avail ? avail : size;
appl_ptr = READ_ONCE(runtime->control->appl_ptr);
appl_ofs = appl_ptr % runtime->buffer_size;
cont = runtime->buffer_size - appl_ofs;
if (frames > cont)
frames = cont;
// 9. 执行实际数据传输
if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) {
err = -EBUSY;
goto _end_unlock;
}
snd_pcm_stream_unlock_irq(substream);
// DMA 同步
if (!is_playback)
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
// 调用写函数传输数据
err = writer(substream, appl_ofs, data, offset, frames,
transfer, in_kernel);
if (is_playback)
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
snd_pcm_stream_lock_irq(substream);
atomic_dec(&runtime->buffer_accessing);
if (err < 0)
goto _end_unlock;
// 更新应用指针
appl_ptr += frames;
if (appl_ptr >= runtime->boundary)
appl_ptr -= runtime->boundary;
err = pcm_lib_apply_appl_ptr(substream, appl_ptr);
if (err < 0)
goto _end_unlock;
// 更新计数器和状态
offset += frames;
size -= frames;
xfer += frames;
avail -= frames;
// 播放流自动启动检查
if (is_playback &&
runtime->state == SNDRV_PCM_STATE_PREPARED &&
snd_pcm_playback_hw_avail(runtime) >= runtime->start_threshold) {
err = snd_pcm_start(substream);
if (err < 0)
goto _end_unlock;
}
}
_end_unlock:
runtime->twake = 0;
if (xfer > 0 && err >= 0)
snd_pcm_update_state(substream, runtime);
snd_pcm_stream_unlock_irq(substream);
return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}2.2 交错式 vs 非交错式传输
交错式 (Interleaved)
文件: /sound/core/pcm_lib.c:2127-2143
// 交错式: L R L R L R ... (L=左声道, R=右声道)
static int interleaved_copy(struct snd_pcm_substream *substream,
snd_pcm_uframes_t hwoff, void *data,
snd_pcm_uframes_t off,
snd_pcm_uframes_t frames,
pcm_transfer_f transfer,
bool in_kernel)
{
struct snd_pcm_runtime *runtime = substream->runtime;
// 转换为字节偏移
hwoff = frames_to_bytes(runtime, hwoff);
off = frames_to_bytes(runtime, off);
frames = frames_to_bytes(runtime, frames);
// 单次传输所有声道数据
return do_transfer(substream, 0, hwoff, data + off, frames,
transfer, in_kernel);
}非交错式 (Non-interleaved)
文件: /sound/core/pcm_lib.c:2148-2177
// 非交错式: L L L L ... R R R R ... (先左声道,后右声道)
static int noninterleaved_copy(struct snd_pcm_substream *substream,
snd_pcm_uframes_t hwoff, void *data,
snd_pcm_uframes_t off,
snd_pcm_uframes_t frames,
pcm_transfer_f transfer,
bool in_kernel)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int channels = runtime->channels;
void **bufs = data; // 每声道独立缓冲区
int c, err;
// 每个声道单独传输
for (c = 0; c < channels; ++c, ++bufs) {
if (!data || !*bufs)
err = fill_silence(substream, c, hwoff, NULL, frames);
else
err = do_transfer(substream, c, hwoff, *bufs + off,
frames, transfer, in_kernel);
if (err < 0)
return err;
}
return 0;
}2.3 DMA 缓冲区管理
关键字段
// /include/sound/pcm.h
struct snd_pcm_runtime {
unsigned char *dma_area; // DMA 区域虚拟地址 (用户空间可见)
dma_addr_t dma_addr; // DMA 物理地址 (总线地址)
size_t dma_bytes; // DMA 区域总字节数
struct snd_dma_buffer *dma_buffer_p; // 分配的缓冲区描述符
};缓冲区同步
文件: /sound/core/pcm_lib.c:2379, 2383
// 传输前同步 (capture) - 确保 CPU 读取最新数据
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
// 传输后同步 (playback) - 确保 DMA 读取最新数据
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);2.4 中断驱动 vs 轮询模式
中断驱动模式
当硬件完成一个周期时会触发中断,调用 snd_pcm_period_elapsed():
文件: /sound/core/pcm_lib.c:1933-1940
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
if (snd_BUG_ON(!substream))
return;
guard(pcm_stream_lock_irqsave)(substream);
snd_pcm_period_elapsed_under_stream_lock(substream);
}更新流程 (pcm_lib.c:1900-1918):
- 调用
snd_pcm_update_hw_ptr0(substream, 1)更新 hw_ptr - 检查 overrun/underrun
- 唤醒等待进程
- 发送 SIGIO 信号
轮询模式 (无周期中断)
当 runtime->no_period_wakeup = true 时:
文件: /sound/core/pcm_lib.c:376-397
if (runtime->no_period_wakeup) {
// 通过时间检测 xruns
jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
if (jdelta < runtime->hw_ptr_buffer_jiffies / 2)
goto no_delta_check;
hdelta = jdelta - delta * HZ / runtime->rate;
xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1;
// 根据时间推算丢失的周期
while (hdelta > xrun_threshold) {
delta += runtime->buffer_size;
hw_base += runtime->buffer_size;
// ...
hdelta -= runtime->hw_ptr_buffer_jiffies;
}
}3. DPCM (Dynamic PCM)
3.1 dpcm 状态机
文件: /include/sound/soc-dpcm.h:38-49
enum snd_soc_dpcm_state {
SND_SOC_DPCM_STATE_NEW = 0, // 新创建
SND_SOC_DPCM_STATE_OPEN, // 已打开
SND_SOC_DPCM_STATE_HW_PARAMS, // 硬件参数已设置
SND_SOC_DPCM_STATE_PREPARE, // 已准备
SND_SOC_DPCM_STATE_START, // 已启动
SND_SOC_DPCM_STATE_STOP, // 已停止
SND_SOC_DPCM_STATE_PAUSED, // 已暂停
SND_SOC_DPCM_STATE_SUSPEND, // 已挂起
SND_SOC_DPCM_STATE_HW_FREE, // 硬件资源已释放
SND_SOC_DPCM_STATE_CLOSE, // 已关闭
};状态转换图
NEW -> OPEN -> HW_PARAMS -> PREPARE -> START
|
v
STOP
|
+--------+-----------+--------+
| | |
v v v
HW_FREE PAUSED SUSPEND
| | |
v v v
CLOSE START (resume) SUSPEND
|
v
HW_FREE -> CLOSE3.2 dpcm_be/dai_link 机制
文件: /include/sound/soc-dpcm.h:68-83
struct snd_soc_dpcm {
struct snd_soc_pcm_runtime *be; // 后端运行时
struct snd_soc_pcm_runtime *fe; // 前端运行时
enum snd_soc_dpcm_link_state state; // 链接状态
struct list_head list_be; // BE 链表节点
struct list_head list_fe; // FE 链表节点
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_state;
#endif
};文件: /include/sound/soc-dpcm.h:88-104
struct snd_soc_dpcm_runtime {
struct list_head be_clients; // 连接的 BE 客户端链表
struct list_head fe_clients; // 连接的 FE 客户端链表
int users; // 用户计数
struct snd_pcm_hw_params hw_params; // 硬件参数
enum snd_soc_dpcm_update runtime_update; // 运行时更新类型
enum snd_soc_dpcm_state state; // DPCM 状态
int trigger_pending; // 待处理的触发命令
int be_start; // BE 启动引用计数
int be_pause; // BE 暂停引用计数
bool fe_pause; // FE 是否已暂停
};3.3 FE/BE 路由
文件: /sound/soc/soc-pcm.c
FE/BE 链接遍历宏
// 遍历所有 FE 客户端
#define for_each_dpcm_fe(be, stream, _dpcm) \
list_for_each_entry(_dpcm, &(be)->dpcm[stream].fe_clients, list_fe)
// 遍历所有 BE 客户端
#define for_each_dpcm_be(fe, stream, _dpcm) \
list_for_each_entry(_dpcm, &(fe)->dpcm[stream].be_clients, list_be)状态检查函数
文件: /sound/soc/soc-pcm.c:59-101
// 检查 FE/BE 是否处于特定状态组合
static int snd_soc_dpcm_check_state(fe, be, stream, states, num_states)
{
// 遍历 FE 关联的所有 BE
for_each_dpcm_fe(be, stream, dpcm) {
if (dpcm->fe == fe)
continue;
state = dpcm->fe->dpcm[stream].state;
// 检查是否匹配任何目标状态
for (i = 0; i < num_states; i++) {
if (state == states[i])
return 1; // 匹配
}
}
return 0;
}
// 检查是否可以启动
static int snd_soc_dpcm_can_be_start(fe, be, stream)
{
const enum snd_soc_dpcm_state state[] = {
SND_SOC_DPCM_STATE_START,
SND_SOC_DPCM_STATE_PAUSED,
SND_SOC_DPCM_STATE_SUSPEND,
};
return snd_soc_dpcm_check_state(fe, be, stream, state, 3);
}4. DAPM (Dynamic Audio Power Management)
4.1 widget 树结构
文件: /include/sound/soc-dapm.h:516-566
struct snd_soc_dapm_widget {
enum snd_soc_dapm_type id; // widget 类型
const char *name; // widget 名称
const char *sname; // 流名称
struct list_head list;
struct snd_soc_dapm_context *dapm; // 所属 DAPM 上下文
void *priv; // 私有数据
/* dapm 控制 */
int reg; // 寄存器地址 (-1 = 无直接控制)
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 new:1; // 新状态完成
unsigned char force:1; // 强制状态
unsigned char ignore_suspend:1; // 忽略挂起
unsigned char new_power:1; // 本次电源状态
unsigned char power_checked:1; // 本次已检查
unsigned char is_supply:1; // 是否为电源类型
unsigned char is_ep:2; // 端点类型
int (*power_check)(struct snd_soc_dapm_widget *w); // 电源检查回调
/* 事件 */
unsigned short event_flags; // 事件标志
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
/* kcontrols */
int num_kcontrols;
const struct snd_kcontrol_new *kcontrol_news;
struct snd_kcontrol **kcontrols;
/* 输入/输出边 */
struct list_head edges[2]; // [输入边, 输出边]
/* DAPM 更新使用 */
struct list_head work_list;
struct list_head power_list;
struct list_head dirty;
int endpoints[2]; // [输入端点数, 输出端点数]
};Widget 类型
文件: /include/sound/soc-dapm.h (部分)
enum snd_soc_dapm_type {
snd_soc_dapm_dai_in, // DAI 输入
snd_soc_dapm_dai_out, // DAI 输出
snd_soc_dapm_dai_link, // DAI 链接
snd_soc_dapm_aif_in, // AIF 输入
snd_soc_dapm_aif_out, // AIF 输出
snd_soc_dapm_vmid, // VMID (偏置)
snd_soc_dapm_micbias, // 麦克风偏置
snd_soc_dapm_mic, // 麦克风输入
snd_soc_dapm_input, // 输入引脚
snd_soc_dapm_output, // 输出引脚
snd_soc_dapm_switch, // 开关
snd_soc_dapm_mixer, // 混音器
snd_soc_dapm_mixer_named_ctl, // 命名控制混音器
snd_soc_dapm_pga, // 可编程增益放大器
snd_soc_dapm_adc, // ADC
snd_soc_dapm_dac, // DAC
snd_soc_dapm_mux, // 多路复用器
snd_soc_dapm_demux, // 解复用器
snd_soc_dapm_supply, // 电源供应
snd_soc_dapm_regulator_supply, // 稳压器
snd_soc_dapm_clock_supply, // 时钟供应
snd_soc_dapm_kcontrol, // kcontrol widget
// ... 更多类型
};4.2 kcontrol 机制
文件: /include/sound/soc-dapm.c:706-772
kcontrol (内核控制) 提供了用户空间与 DAPM widget 之间的接口。
// kcontrol 数据结构
struct dapm_kcontrol_data {
struct snd_soc_dapm_widget *widget; // 关联的 widget
struct list_head paths; // 路径列表
unsigned int value; // 当前值
};4.3 path_power() 电源状态机
文件: /sound/soc/soc-dapm.c:2176-2220
static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,
struct list_head *up_list,
struct list_head *down_list)
{
struct snd_soc_dapm_path *path;
int power;
// 特殊类型处理
switch (w->id) {
case snd_soc_dapm_pre:
power = 0; // pre widget 始终断电
goto end;
case snd_soc_dapm_post:
power = 1; // post widget 始终上电
goto end;
default:
break;
}
// 调用 widget 的 power_check 回调确定电源状态
power = dapm_widget_power_check(w);
// 状态未变则跳过
if (w->power == power)
return;
trace_snd_soc_dapm_widget_power(w, power);
// 更新对等 widget 的电源状态
snd_soc_dapm_widget_for_each_source_path(w, path)
dapm_widget_set_peer_power(path->source, power, path->connect);
// 供应类型不影响输出
if (!w->is_supply)
snd_soc_dapm_widget_for_each_sink_path(w, path)
dapm_widget_set_peer_power(path->sink, power, path->connect);
end:
// 加入对应的电源列表
if (power)
dapm_seq_insert(w, up_list, true); // 上电序列
else
dapm_seq_insert(w, down_list, false); // 下电序列
}电源检查函数
文件: /sound/soc/soc-dapm.c:1721-1776
static int dapm_widget_power_check(struct snd_soc_dapm_widget *w)
{
if (w->power_checked)
return w->power;
if (w->force)
return 1;
if (w->power_check)
return w->power_check(w);
// 根据类型使用默认检查函数
return dapm_generic_check_power(w);
}4.4 事件驱动 vs 路径遍历
事件驱动模式
当用户空间操作 (如播放/暂停) 触发:
文件: /sound/soc/soc-dapm.c:2252-2329
static int dapm_power_widgets(struct snd_soc_card *card, int event,
struct snd_soc_dapm_update *update)
{
// 遍历所有标记为 dirty 的 widget
list_for_each_entry(w, &card->dapm_dirty, dirty) {
dapm_power_one_widget(w, &up_list, &down_list);
}
// 根据事件应用序列
switch (event) {
case SND_SOC_DAPM_STREAM_START:
// 启动流: 上电相关 widgets
break;
case SND_SOC_DAPM_STREAM_STOP:
// 停止流: 下电 widgets
break;
}
}路径遍历 (Path Walk)
文件: /sound/soc/soc-dapm.c:1550-1580
// 遍历从 widget 到活动端点的所有路径
static int dapm_is_connected_output_ep(...)
{
// 使用 BFS/DFS 遍历路径树
// 寻找从 source 到有效 sink 的路径
}5. ASoC DAI (Digital Audio Interface)
5.1 struct snd_soc_dai 完整结构
文件: /include/sound/soc-dai.h:438-470
struct snd_soc_dai {
const char *name; // DAI 名称
int id; // DAI ID
struct device *dev; // 设备
struct snd_soc_dai_driver *driver; // DAI 驱动
struct snd_soc_dai_stream stream[SNDRV_PCM_STREAM_LAST + 1];
// stream[0] = playback, stream[1] = capture
/* 对称性约束 */
unsigned int symmetric_rate;
unsigned int symmetric_channels;
unsigned int symmetric_sample_bits;
struct snd_soc_component *component; // 父组件
struct list_head list; // 链表节点
/* 标记 */
struct snd_pcm_substream *mark_startup;
struct snd_pcm_substream *mark_hw_params;
struct snd_pcm_substream *mark_trigger;
unsigned int probed:1; // 是否已探测
void *priv; // 私有数据
};DAI 流结构
struct snd_soc_dai_stream {
struct snd_soc_dapm_widget *widget; // 关联的 DAPM widget
unsigned int active; // 活动计数
unsigned int tdm_mask; // TDM slot 掩码
void *dma_data; // DMA 私有数据
};5.2 DAI hw_params()
文件: /sound/soc/soc-dai.c + /include/sound/soc-dai.h
// /include/sound/soc-dai.h:204-206
int snd_soc_dai_hw_params(struct snd_soc_dai *dai,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);这是 DAI 层设置硬件参数的函数,在 PCM hw_params 期间调用。
5.3 DAI Operations
文件: /include/sound/soc-dai.h:269-366
struct snd_soc_dai_ops {
/* 探测/移除 */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
/* PCM 新建 */
int (*pcm_new)(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
/* 时钟配置 */
int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
/* 格式配置 */
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, const unsigned int *tx_slot,
unsigned int rx_num, const unsigned int *rx_slot);
/* 流控制 */
int (*set_stream)(struct snd_soc_dai *dai, void *stream, int direction);
void *(*get_stream)(struct snd_soc_dai *dai, int direction);
/* 静音控制 */
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
/* PCM 操作 */
int (*startup)(struct snd_pcm_substream *, struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *);
/* 延迟报告 */
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/* 格式自动选择 */
const u64 *auto_selectable_formats;
int num_auto_selectable_formats;
};操作时序
启动流程:
startup() -> hw_params() -> prepare() -> trigger(START) -> 运行时
停止流程:
trigger(STOP) -> hw_free() -> shutdown()
参数变更流程:
hw_free() -> hw_params() -> prepare()5.4 时钟配置流程
文件: /sound/soc/soc-dai.c:38-51
int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
int ret;
if (dai->driver->ops && dai->driver->ops->set_sysclk)
ret = dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
else
ret = snd_soc_component_set_sysclk(dai->component, clk_id, 0,
freq, dir);
return ret;
}时钟配置层级:
- set_sysclk - 设置系统/主时钟 (MCLK)
- set_pll - 配置 PLL 从输入时钟生成输出时钟
- set_clkdiv - 设置时钟分频器
- set_bclk_ratio - 设置 BCLK 与采样率比率
6. 性能优化
6.1 DMA Scatter-Gather
ALSA PCM 通过 struct snd_dma_buffer 支持分散-聚集 DMA:
文件: /include/sound/pcm.h
struct snd_dma_buffer {
struct device *dev; // 设备
unsigned char *area; // 虚拟地址
dma_addr_t addr; // 物理/总线地址
size_t bytes; // 缓冲区大小
enum snd_dma_buffer_type dev.type; // 缓冲区类型
void *private_data; // 私有数据
};6.2 零拷贝机制
mmap 零拷贝路径:
用户空间 内核空间
| |
|-- mmap(PCM) --------->| // 建立映射
| |
| read()/write() | // 传统拷贝
| |
|<-- mmap 共享内存 ----->| // 零拷贝
| (DMA 直接访问) |文件: /sound/core/pcm_lib.c:2098-2122
// 直接 DMA 传输 (零拷贝)
static int do_transfer(struct snd_pcm_substream *substream, int c,
unsigned long hwoff, void *data, unsigned long bytes,
pcm_transfer_f transfer, bool in_kernel)
{
struct iov_iter iter;
int err, type;
// 设置迭代器类型
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
type = ITER_SOURCE;
else
type = ITER_DEST;
if (in_kernel) {
// 内核内部传输
struct kvec kvec = { data, bytes };
iov_iter_kvec(&iter, type, &kvec, 1, bytes);
return transfer(substream, c, hwoff, &iter, bytes);
}
// 用户空间 -> 内核 (零拷贝)
err = import_ubuf(type, (__force void __user *)data, bytes, &iter);
if (err)
return err;
return transfer(substream, c, hwoff, &iter, bytes);
}6.3 缓冲区 mmap
文件: /sound/core/pcm_native.c
关键 mmap 函数:
snd_pcm_mmap()- 通用 mmap 入口snd_pcm_mmap_data()- PCM 数据缓冲区映射
// mmap 系统调用处理
static int snd_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *area)
{
// 验证 mmap 能力
if (!hw_support_mmap(substream))
return -ENXIO;
// 调用驱动特定 mmap 或使用默认
if (substream->ops->mmap)
return substream->ops->mmap(substream, area);
else if (substream->ops->page)
return snd_pcm_mmap_data(substream, file, area);
else
return -ENXIO;
}6.4 中断合并
周期中断模式
文件: /sound/core/pcm_lib.c:343-359
// 中断处理时的双确认检测
if (in_interrupt) {
delta = runtime->hw_ptr_interrupt + runtime->period_size;
if (delta > new_hw_ptr) {
hdelta = curr_jiffies - runtime->hw_ptr_jiffies;
if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) {
// 跳过期间发生了另一次中断
hw_base += runtime->buffer_size;
if (hw_base >= runtime->boundary) {
hw_base = 0;
crossed_boundary++;
}
new_hw_ptr = hw_base + pos;
goto __delta;
}
}
}无周期中断模式 (polling)
文件: /sound/core/pcm_lib.c:376-397
当 runtime->no_period_wakeup = true 时,使用基于时间的中断合并和 xrun 检测:
if (runtime->no_period_wakeup) {
jdelta = curr_jiffies - runtime->hw_ptr_jiffies;
if (jdelta < runtime->hw_ptr_buffer_jiffies / 2)
goto no_delta_check;
// 通过时间差推算错过的周期数
hdelta = jdelta - delta * HZ / runtime->rate;
while (hdelta > xrun_threshold) {
delta += runtime->buffer_size;
hw_base += runtime->buffer_size;
// ...
}
}附录 A: 关键数据结构关系图
┌─────────────────────────────────────────────────────────────────┐
│ snd_pcm │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ streams[2]: │ │
│ │ [PLAYBACK] ──> snd_pcm_str │ │
│ │ [CAPTURE] ──> snd_pcm_str │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ snd_pcm_str │
│ substream_count: 子流数量 │
│ substream_opened: 已打开子流数量 │
│ substream: 指向第一个子流的指针 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ snd_pcm_substream │
│ runtime: 指向运行时信息的指针 │
│ ops: PCM 操作函数集 │
│ dma_buffer: DMA 缓冲区描述符 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ snd_pcm_runtime │
│ state: 当前状态 (OPEN/SETUP/PREPARED/RUNNING/XRUN/DRAINING) │
│ hw: 硬件能力描述 │
│ dma_area: DMA 缓冲区虚拟地址 │
│ dma_addr: DMA 缓冲区物理地址 │
│ status: 状态信息 (hw_ptr, tstamp) ──> mmap 到用户空间 │
│ control: 控制信息 (appl_ptr, avail_min) ──> mmap 到用户空间 │
│ hw_constraints: 硬件约束规则 │
└─────────────────────────────────────────────────────────────────┘附录 B: DPCM FE/BE 路由关系图
+------------------+ +------------------+
| App (用户空间) | | HW (硬件) |
+--------+---------+ +--------+---------+
│ │
│ write()/read() │
│ mmap() │
v v
+------------------+ +------------------+
| Frontend (FE) | | Backend (BE) |
| snd_pcm_runtime | | snd_poc_runtime |
+--------+---------+ +--------+---------+
│ │
│ dpcm_link │
+-------------------------+
│ snd_soc_dpcm │
│ fe ──> be │
+-------------------------+
│
▼
+------------------+
| DAPM paths |
| (widgets) |
+------------------+
│
▼
+------------------+
| snd_soc_dai |
| (CPU/CODEC DAI) |
+------------------+附录 C: DAPM Widget 电源状态机
┌─────────────────────────────────────┐
│ dapm_power_widgets() │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 1. 确定初始 bias_level │
│ (STANDBY/BIAS_ON/BIAS_OFF) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. 遍历 dapm_dirty 列表 │
│ 调用 dapm_power_one_widget() │
└─────────────────────────────────────┘
│
┌─────────────────┴─────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ power_check() │ │ power_check() │
│ 返回需要上电? │ │ 返回需要上电? │
└──────────────────┘ └──────────────────┘
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ up_list │ │down_list│ │ up_list │ │down_list│
│ 追加 │ │ 追加 │ │ 追加 │ │ 追加 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
└─────────┬─────────┘ └─────────┬─────────┘
│ │
└─────────────────┬─────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. 按 seq 顺序执行电源序列 │
│ dapm_up_seq[] / dapm_down_seq[] │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 4. 调用各 widget 的 power_check() │
│ 更新 power 状态 │
└─────────────────────────────────────┘附录 D: DAI 时钟配置流程
应用程序
│
▼
snd_pcm_hw_params()
│
▼
┌───────────────────────────────────────┐
│ snd_soc_pcm_hw_params() │
│ 1. FE DAI hw_params │
│ 2. BE DAI hw_params │
│ 3. DPCM 路由和参数同步 │
└───────────────────────────────────────┘
│
├──> snd_soc_dai_set_sysclk() // 设置 MCLK/SYSCLK
│ │
│ ▼
│ ┌─────────────────────────────┐
│ │ DAI driver->set_sysclk() │
│ │ 或 Component set_sysclk() │
│ └─────────────────────────────┘
│
├──> snd_soc_dai_set_pll() // 配置 PLL (如果需要)
│ │
│ ▼
│ ┌─────────────────────────────┐
│ │ DAI driver->set_pll() │
│ │ 或 Component set_pll() │
│ └─────────────────────────────┘
│
├──> snd_soc_dai_set_clkdiv() // 设置分频
│ │
│ ▼
│ ┌─────────────────────────────┐
│ │ DAI driver->set_clkdiv() │
│ └─────────────────────────────┘
│
└──> snd_soc_dai_set_fmt() // 设置格式
│
▼
┌─────────────────────────────┐
│ DAI driver->set_fmt() │
│ 设置 I2S/SPDIF/DSP 格式 │
└─────────────────────────────┘文档结束