Linux 驱动之 ALSA(九)虚拟声卡 Latency

测试方法

  • 两块板子之间通过 I2S 通信,其中一块板子配置为 Slave 另一块板子为 Master

  • Master 板端执行 arecord | aplay 或者 gstream 命令

    1
    2
    arecord -Dhw:2,0 -r 44100 -c 8 -f S32_LE | aplay -Dhw:2,0 -c 8 -r 44100 -f S32_LE
    gst-launch-1.0 alsasrc device=device_input_split ! alsasink device=device_output sync=false

  • Slave 通过 aplay 播放一段 wav 文件

  • 通过示波器抓取 Master 板的 I2S 输入和输出端的波形间隔来测量虚拟声卡的 Latency

DataStream

上图就是 arecord | aplay(没有使用插件) 整个虚拟声卡的数据流,其中延迟最大的部分就是红色框中的缓存。

走 USB 通路的 latency:

增大 Buffer Size 为什么会增大 latency?

aplay 通过调用 snd_pcm_write 写数据给驱动,alsa lib 中 snd_pcm_write 有如下调用栈:

1
2
3
4
_snd_pcm_writei(pcm, buffer, size)
-->pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size)
-->snd_pcm_hw_writei
-->ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi)

alsa lib 通过 ioctl 系统调用到内核,打印 snd_pcm_write 内核调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
   14.513126@2]  Call trace:
[ 14.513472@2] [ffffffc0201cbb20+ 96][<ffffffe610095490>] dump_backtrace+0x0/0x188
[ 14.514417@2] [ffffffc0201cbb80+ 32][<ffffffe61009563c>] show_stack+0x24/0x30
[ 14.515329@2] [ffffffc0201cbba0+ 64][<ffffffe611010068>] dump_stack+0xc8/0xf0
[ 14.516240@2] [ffffffc0201cbbe0+ 208][<ffffffe610cc4d14>] __snd_pcm_lib_xfer+0x834/0x850
[ 14.517257@2] [ffffffc0201cbcb0+ 80][<ffffffe610cb9a34>] snd_pcm_ioctl_xferi_compat+0x14c/0x260
[ 14.518360@2] [ffffffc0201cbd00+ 208][<ffffffe610cbd66c>] snd_pcm_ioctl_compat+0x10c/0xdb8
[ 14.519405@2] [ffffffc0201cbdd0+ 144][<ffffffe610323754>] __arm64_compat_sys_ioctl+0x16c/0x1220
[ 14.520496@2] [ffffffc0201cbe60+ 64][<ffffffe61009ee28>] el0_svc_common.constprop.3+0x90/0x1b0
[ 14.521589@2] [ffffffc0201cbea0+ 336][<ffffffe61009f014>] el0_svc_compat_handler+0x2c/0x38
[ 14.522630@2] [ffffffc0201cbff0+ 0][<ffffffe610083d5c>] el0_svc_compat+0x8/0x2c

整理一下:

内核中通过 Ioctl 系统调用最终调用到__snd_pcm_lib_xfer 函数,然后发现该数有如下逻辑:

如果是 playback 流,且状态为 parpare,当 runtime→dma_area 里的数据大于等于 runtime→start_threshold,状态才会切换到 running。通过打印 log 发现:

1
2
[   14.571395@0]  playback : buffer_size : 2048 dma size : 65536 start_threshold : 2048 stop_threshold : 2048
[ 14.572960@2] capture : buffer_size : 2048 dma size : 65536 start_threshold : 1 stop_threshold : 2048

playback 的 runtime→start_threshold 跟 buffer_size 一样大,也就是 playback 会先缓存 buffer_size 个 frame 的数据然后再开始播放,这样会导致 latency 随着 buffer_size 的变大而变大了。

参考文献

https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
https://alsa.opensrc.org/ALSA_plugins