数据结构

原图

  • snd_card: 是最顶层数据结构,通过链表挂载该 sound card 的所有设备 snd_device。
  • snd_pcm: 挂在 snd_card 下面的一个 snd_device。
  • snd_pcm_str: 代表 playback stream 和 capture stream。
  • snd_pcm_substream: 是 pcm 中间层的核心,绝大部分任务都是在 substream 中处理,尤其是他的 ops(snd_pcm_ops)字段,许多 user 空间的应用程序通过 alsa-lib 对驱动程序的请求都是由该结构中的函数处理。它的 runtime 字段则指向 snd_pcm_runtime 结构,snd_pcm_runtime 记录这 substream 的一些重要的软件和硬件运行环境和参数。
  • snd_pcm_ops: 创建声卡需要提供的回调。
  • snd_pcm_runtime: 运行时参数,包括 sample rate、channel、format 等参数,以及 buffer 相关信息。
  • snd_pcm_harward: 硬件相关参数,创建声卡需要提供相关参数。

HW Buffer

当 app 在调用 snd_pcm_writei 时,alsa core 将 app 传来的数据搬到 HW buffer(即 DMA buffer)中,alsa driver 从 HW buffer 中读取数据传输到硬件播放。

ALSA buffer 是采用 ring buffer 来实现的。ring buffer 有多个 HW buffer 组成。HW buffer 一般是在 alsa driver 的 hw_params 函数中分配的一块大小为 buffer size 的 DMA buffer. 之所以采用多个 HW buffer 来组成 ring buffer, 是防止读写指针的前后位置频繁的互换(即写指针到达 HW buffer 边界时,就要回到 HW buffer 起始点)。

ring buffer = n * HW buffer. 通常这个 n 比较大,在数据读写的过程中,很少会出现读写指针互换的情况。下图是 ALSA buffer 的实现以及读写指针更新的方法:

  • hw_ptr_base: 是当前 HW buffer 在 Ring buffer 中的起始位置。当读指针到达 HW buffer 尾部时,hw_ptr_base 按 buffer size 移动。
  • hw_ptr: 即 HW buffer 的读指针。alsa driver 将数据从 HW buffer 中读走并送到声卡硬件时,hw_ptr 就会移动到新位置。
  • appl_ptr: 即 HW buffer 的写指针。app 在调用 snd_pcm_write 写数据,alsa core 将数据 copy 到 HW buffer 后,appl_ptr 就更新。
  • boundary: 即 Ring buffer 边界。
  • hw_ofs: 是读指针在当前 HW buffer 中的位置。由 alsa driver 的 pointer() 返回。
  • appl_ofs: 是写指针在当前 HW buffer 中的位置。

hw_ptr 的更新是通过调用 snd_pcm_update_hw_ptr() 完成。此函数在 app 写数据时会调用,也会在硬件中断时通过 snd_pcm_peroid_elapsed 调用

流程

原图

  • 分配并设置 snd_card 数据结构
  • 创建 snd_pcm instance
  • 设置 snd_pcm_ops 结构体成员
  • 分配 DMA 内存
  • 注册声卡

参考文献

《Linux 设备驱动开发-约翰-马德奥》