我试图保持sox
声卡的管道输入打开,并仅在管道中有声音时才执行播放器命令(不杀死管道或使用文件)。
这可以使用sox silence 1 0.1 5% -1 0.1 5%
for 文件轻松实现,但是当我将它用于管道输出时,它不起作用。
这是sox
我正在使用的记录命令
/bin/sox -V2 -q \
-r 48000 -b 16 -c 2 -t alsa hw:CARD=sndrpihifiberry,DEV=0 \
-t wav -r 44100 -b 16 -c 2 - \
silence 1 0.1 0.1% -1 2 0.5% \
> $streamFile &
我想仅当管道中有声音时才将播放器连接到管道或从管道上分离。就像是:
while [ true ]; do
until [ WAIT FOR SOUND ]; do
TEST FOR SOUND IN THE PIPE
done
echo "Sound Detected starting @ $(date)" >> $log
/usr/bin/player < $streamFile &
PLAYERpid=$!
until [ WAIT FOR SILENCE ]; do
TEST FOR SILENCE IN THE PIPE
done
kill $PLAYERpid
echo "Silence Detected killing PLAYER @ $(date)" >> $log
done
有任何想法吗?
答案1
如果肮脏的黑客就足够了......
我尊重马库斯的回应,但如果肮脏的黑客足够好,你可以尝试这个。
注意事项:
- 需要 PulseAudio 或 PipeWire(安装了pulseaudio utils)
- 需要 ffmpeg 进行静音检测
- 在播放器开始播放之前,您将失去一小部分声音
- 静音检测可能需要一些调整,以避免在音频中间的安静时段杀死玩家
1. 检测音频
您实际上不需要在这里进行任何花哨的信号处理,您可以简单地记录声音设备的低质量捕获并将其直接传递给 grep,后者仅检查是否存在任何事物那里...
#!/bin/bash
# Get your PulseAudio source monitor (edit regex to suit you)
pulse_monitor=$(pactl list short sources | awk '$1 = /alsa.*monitor/ {print $2}')
function wait_for_audio() {
parec --rate 1000 -d $pulse_monitor 2>/dev/null \
| LC_ALL=C fgrep -qm 1 .
}
while [ true ]; do
wait_for_audio
echo "Sound Detected starting @ $(date)" >> $log
/usr/bin/player < $streamFile &
PLAYERpid=$!
...
done
这对我有用,延迟非常低,因为一旦数据的第一个字节通过, fgrep 就会退出管道。从那时开始玩家显然会输一些音频,但在我的测试中它已经可以接受。这是最简单的部分。
2. 检测静音
这有点困难,因为我们不想在检测到“有东西”时退出管道,而是想等到它检测到“没有”。我们这里不能使用 grep。检测静音的一种方法是使用ffmpeg
,它具有可配置的静音检测过滤器。然而,将 ffmpeg 添加到管道会使事情变得复杂,因为parec | ffmpeg | fgrep -m1
当检测到某些内容时, using 不会退出fgrep -m1
。您会看到,虽然在终止parec
后以 sigpipe 退出,但不会,并且 bash 在所有命令完成之前不会从管道返回。因此我们将使用进程替换而不是管道。另外,ffmpeg 的噪音非常大,它的静音检测输出到 stderr 而不是 stdout,所以我们还要交换 ffmpeg 的 stderr 和 stdoutfgrep
ffmpeg
detect_silence() {
# By default, exit after detecting 2 seconds of continuous silence
SECONDS=${1:-2}
LC_ALL=C fgrep -m 1 silence_start \
<(parec \
--rate 1000 \
--raw \
-d ${pulse_monitor} 2>/dev/null \
| ffmpeg \
-hide_banner \
-f s8 \
-ar 1k \
-ac 2 \
-i pipe: \
-af silencedetect=noise=-50dB:d=${SECONDS} \
-f null - \
3>&1 1>&2 2>&3)
}
CPU开销
音频检测 <0.3%
音频检测开销非常低。我以 1khz 速率对音频进行采样,因为我不关心质量,我只想要一小部分原始数据。我通过使用fgrep
with来减少开销LC_ALL=C
(比 bare 快约 1400% grep
)。在我的 Raspberry Pi 4 上,pulseaudio CPU 在运行的前几秒内约为 0.3%。之后,它又跌回几乎为零。
静音检测 ~1% CPU
静音检测开销稍高,因为当我运行 ffmpeg 时,ffmpeg 在我的 Raspberry Pi 4 上使用了约 1% 的 CPU。
用我的技巧完成命令
#!/bin/bash
# Get your PulseAudio source monitor (edit regex to suit you)
pulse_monitor=$(pactl list short sources | awk '$1 = /alsa.*monitor/ {print $2}')
function wait_for_audio() {
# sample audio at 1khz and exit as soon as data is detected
parec --rate 1000 -d $pulse_monitor 2>/dev/null \
| LC_ALL=C fgrep -qm 1 .
}
detect_silence() {
# By default, exit after detecting 2 seconds of continuous silence
SECONDS=${1:-2}
LC_ALL=C fgrep -m 1 silence_start \
<(parec \
--rate 1000 \
--raw \
-d ${pulse_monitor} 2>/dev/null \
| ffmpeg \
-hide_banner \
-f s8 \
-ar 1k \
-ac 2 \
-i pipe: \
-af silencedetect=noise=-50dB:d=${SECONDS} \
-f null - \
3>&1 1>&2 2>&3)
}
while [ true ]; do
wait_for_audio
echo "Sound Detected starting @ $(date)" >> $log
/usr/bin/player < $streamFile &
PLAYERpid=$!
detect_silence 2
echo "Silence Detected starting @ $(date -d "2 seconds ago")" >> $log
kill $PLAYERpid
"Silence Detected killing PLAYER @ $(date)" >> $log
done
答案2
老实说,信号处理不应该尝试在 shell 脚本中实现。它依赖于对一堆二进制数据定期执行算术。即使每秒运行这个循环数百次,您的计算机也会遇到困难,而这将允许您进行连续的信号处理。
所以,抱歉,这行不通。
您可以使用许多非常好的框架之一来轻松地完成此操作,但由于上述原因,它们都不会为您提供 shell 脚本。没关系,您不需要 shell 脚本,您需要的是有用的东西!
我鼓励你尝试一下GNU 电台。尽管其重点是软件定义无线电,但它对于设计简单的音频处理工具包来说还是相当不错的。如果快速为您构建这个;它对我有用,如下所述,但您可能需要调整“阈值”高(开始)和低(停止)值。
上面只是一个例子:一旦输入音量超过最大可能值的 10%,它就会开始发出声音,并在低于 5% 时停止。
当您点击 GNU Radio Companion(这是用于设计这些信号处理流程图的工具)中的“生成”按钮时,您会得到一个可执行脚本 - 不是 bash 脚本,而是 Python,但这对您的系统没有任何影响(这就是文件开头的这些#!/usr/bin/env python
或行的用途:告诉您的系统如何运行脚本)。#!/bin/bash
你可以用它构建的东西并没有真正的限制——有读取音频文件的源,有更多的信号处理方式,如果你需要做一些 GNU Radio 本身无法完成的事情,用 Python 编写一个块并不是“其实没那么难!