![使用 Pulseaudio 和 Sox 进行低延迟实时声音过滤](https://linux22.com/image/227145/%E4%BD%BF%E7%94%A8%20Pulseaudio%20%E5%92%8C%20Sox%20%E8%BF%9B%E8%A1%8C%E4%BD%8E%E5%BB%B6%E8%BF%9F%E5%AE%9E%E6%97%B6%E5%A3%B0%E9%9F%B3%E8%BF%87%E6%BB%A4.png)
我使用 Linux 进行音频实验,所以使用 PulseAudio 和 ALSA。我无法实现一致的低延迟。
我有一个想法,可以通过使用我的计算机在频谱的同一部分产生噪声来掩盖不需要的环境噪声(例如警报器或备用警报)。
一种非常简单的方法是将输入样本乘以一些幂律噪声,例如“粉红噪声”(1/f) 或“棕色噪声”(1/f^2),然后从扬声器中播放结果。我认为这对应于频域中的卷积,因此它应该具有使频率尖峰更宽且不那么烦人的效果。
我不是 PulseAudio 的忠实粉丝,但它是 Linux 中标准的应用程序级音频框架,而且它似乎是能够进行可变速率重采样的最简单的工具。重采样用于纠正使用多个设备(在本例中为麦克风和扬声器)时的时钟偏差。我得到了一些减少延迟的建议对于 PulseAudio 在这里和对于 Unix 管道在这里。
我有一个 Sox 命令可以实现我想要的过滤效果,但我不知道如何让 PulseAudio 的输入和输出具有可预测的延迟。下面的简化 (Zsh) 管道命令只是将样本直接从麦克风发送到扬声器,但有时当我运行它时,主观延迟几乎可以忽略不计,有时延迟接近 500 毫秒(例如,如果我在麦克风前面打响指)麦克风,我可能会在某些运行中立即听到它,而在其他运行中它会每秒回声两次)。当我刚刚重新启动管道时,就会出现这些差异;我不必重新启动 PulseAudio 服务器。
PFMT=(--rate 48000 --format s16le --channels 1)
pacat -r --latency-msec=1 $PFMT | pacat --latency-msec=1 $PFMT
我尝试stdbuf -o64 -i64
在每个之前放置pacat
,以防问题是由 Unix 管道缓冲区引起的,但这似乎并没有改变行为。
我总是可以终止管道并重新启动它,并不断重复,直到管道以低延迟启动,但如果有一个每次都有效的解决方案就好了。我无法从 PulseAudio 日志中找出高延迟运行和低延迟运行之间的区别。
从低延迟运行(第一行是虚拟“监视器”源):
$ (pactl list sources; pactl list sinks) | grep Latency
Latency: 0 usec, configured 1999818 usec
Latency: 4193 usec, configured 66000 usec
Latency: 2861 usec, configured 15012 usec
从高延迟运行:
$ (pactl list sources; pactl list sinks) | grep Latency
Latency: 0 usec, configured 1999818 usec
Latency: 505 usec, configured 66000 usec
Latency: 3305 usec, configured 15012 usec
以下是 PulseAudio 配置中的一些相关行,我从互联网建议中复制了这些行。我不确定它们中的任何一个是否有效果。
# .config/pulse/daemon.conf
;; https://forums.linuxmint.com/viewtopic.php?t=44862
default-fragments = 2
default-fragment-size-msec = 5
high-priority = yes
rlimit-nice = 31
nice-level = -11
realtime-scheduling = yes
rlimit-rtprio = 9
realtime-priority = 9
我正在运行一个已有几年历史的 PulseAudio 版本,因此如果我遇到了一些已修复的已知错误,请告诉我。
这是我想要运行的完整噪声乘法命令(再次是 Zsh),它与上面的简单管道一样遇到了不可预测的延迟问题。它与我当前遇到的延迟问题并不真正相关,但这就是为什么我不只是使用 PulseAudio 将module-loopback
样本从源路由到接收器的原因。
SFMT=(-e signed -r 48000 -b 16 -c 1 -t raw)
PFMT=(--rate 48000 --format s16le --channels 1)
STDB=(stdbuf -o64 -i64)
sox -n $SFMT - synth brownnoise vol 0.01 | sox --buffer 64 -T $SFMT - $SFMT <($STDB pacat -r --latency-msec=1 $PFMT) $SFMT >($STDB pacat --latency-msec=1 $PFMT) vol 100
谢谢。
2023 年 12 月 5 日更新:
回答评论中有关我的音频设置和 ALSA 的一些问题。输入是 USB 麦克风“JMTek, LLC. USB PnP 音频设备”,输出是我的笔记本电脑的内置 3.5 毫米音频插孔,通过 AMD 音频控制器。
如果我使用 ALSA(aplay、arecord),那么它似乎可以更一致地实现低延迟,但即使我使用 -B 将缓冲区大小缩短为50微秒。此外,与 Pulse 不同,ALSA 的明显延迟有时会在几分钟内逐渐增加(例如从 10 毫秒到 100 毫秒)。与 Pulse 一样,ALSA 也存在从一次调用到另一次调用的随机延迟变化的问题 - 有时我得到 <10ms,有时 >400ms - 但正如我所说,ALSA 的延迟似乎更常见。这是我用来试验 ALSA 的 shell 代码。请注意,我直接从设备读取数据或向设备写入数据,而不使用“插头”PCM 来更改速率或通道数。
#!/bin/zsh
SFMT=(-e signed -r 48000 -b 16 -c 1 -t raw)
AFMT=(-r 48000 -f S16_LE -c 1)
AOPT=(-B 50 $AFMT)
STDB=(stdbuf -o64 -i64)
sox -n $SFMT - synth brownnoise vol 0.01 | sox --buffer 64 -T $SFMT - $SFMT <($STDB arecord $AOPT -Dhw:2) $SFMT -c 2 >($STDB aplay $AOPT -c 2 -Dhw:1) vol 300
上述 ALSA 实验的输出示例:
Recording WAVE 'stdin' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
Playing raw data 'stdin' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo
underrun!!! (at least 18304.245 ms long)
underrun!!! (at least 870598.858 ms long)
underrun!!! (at least 241414.917 ms long)
underrun!!! (at least 1.451 ms long)
underrun!!! (at least 12.687 ms long)
overrun!!! (at least 4.934 ms long)
underrun!!! (at least 10.253 ms long)
underrun!!! (at least 11.326 ms long)
overrun!!! (at least 0.549 ms long)
...