PipeWire/示例
环绕声系统
分离前置与后置声道
当使用 PipeWire 替代 PulseAudio/JACK 时,你可以配置 PipeWire 来复制 PulseAudio 的前置/后置分离示例。这样一来,你就可以将使用 PulseAudio 的音频流发送到扬声器或耳机的独立输出端(Sink)。
将扬声器连接到线路输出(line-out)端口,耳机连接到后置端口。在 pavucontrol 中将所使用的声卡设置为 Analog Surround 4.0 Output。
使用 PipeWire 配置文件
创建额外的 PipeWire 配置文件 ~/.config/pipewire/pipewire.conf.d/speakers-headphones.conf 或 /etc/pipewire/pipewire.conf.d/speakers-headphones.conf,写入以下内容并根据实际情况修改 target.object 行:
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.name = "speakers"
node.description = "Speakers"
capture.props = {
media.class = "Audio/Sink"
audio.position = [ FL FR ]
}
playback.props = {
target.object = "alsa_output.pci-0000_00_14.2.analog-surround-40"
stream.dont-remix = true
node.passive = true
audio.position = [ FL FR ]
}
}
}
{ name = libpipewire-module-loopback
args = {
node.name = "headphones"
node.description = "Headphones"
capture.props = {
media.class = "Audio/Sink"
audio.position = [ FL FR ]
}
playback.props = {
target.object = "alsa_output.pci-0000_00_14.2.analog-surround-40"
stream.dont-remix = true
node.passive = true
audio.position = [ RL RR ]
}
}
}
]
重启 PipeWire 后,你应该会在 pavucontrol 中看到两个新的输出。类似的示例可以在 [[1](https://docs.pipewire.org/page_module_loopback.html) PipeWire Loopback 模块文档]中找到。
如果你使用的是 pipewire-pulse包,你可能还需要通过编辑 ~/.config/pipewire/pipewire-pulse.conf 或在 /etc/pipewire/pipewire-pulse.conf.d/ 中创建配置文件来启用上混音(upmixing):
stream.properties = {
channelmix.upmix = true
channelmix.upmix-method = psd # none, simple
channelmix.lfe-cutoff = 150
channelmix.fc-cutoff = 12000
channelmix.rear-delay = 12.0
}
这些选项及更多的解释可以通过以下命令查看:
$ man pipewire-props
使用 pactl 和 pw-link
使用以下命令为扬声器和耳机创建新的输出端,并将扬声器链接到前置声道、将耳机链接到后置声道:
pactl load-module module-null-sink sink_name=speakers object.linger=1 media.class=Audio/Sink channel_map=FL,FR pactl load-module module-null-sink sink_name=headphones object.linger=1 media.class=Audio/Sink channel_map=RL,RR
object.linger=1 可以使输出端在创建它的客户端断开连接后保持存活。你可以将 sink_name 命名为任意名称。
要卸载模块,你可以使用 pw-cli destroy ID,其中 ID 是 pactl load-module 命令的输出。目前暂不支持通过 pactl unload-module 单独卸载模块 [[2](https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/709)]。不过,你可以使用 pactl unload-module module-null-sink 来卸载所有 module-null-sink 模块。
使用 jack_connect,将新输出端的监听器连接到声卡的播放端口。通过运行 pw-link -iol 来查明声道的名称 [[3](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Migrate-JACK)]。
pw-link speakers:monitor_1 alsa_output.pci-0000_00_14.2.analog-surround-40:playback_FL pw-link speakers:monitor_2 alsa_output.pci-0000_00_14.2.analog-surround-40:playback_FR pw-link headphones:monitor_1 alsa_output.pci-0000_00_14.2.analog-surround-40:playback_RL pw-link headphones:monitor_2 alsa_output.pci-0000_00_14.2.analog-surround-40:playback_RR
alsa_output.pci-0000_00_14.2.analog-surround-40 替换为你声卡的名称。在脚本执行前添加延迟也可能会使运行更加平稳。alsa_output.pci-0000_00_14.2.analog-surround-40:playback_FL
有时会变成 Built-in\ Audio\ Analog\ Surround\ 4.0:playback_FL。作为临时变通方法,你可以在自动启动脚本中添加第二套使用备用名称的 pw-jack 命令。要单独控制音量,一种选择是使用 ALSA 工具(例如 amixer(1))来控制 Front(前置)和 Rear/Surround(后置/环绕,ALSA 命名)声道。可以在[[4](https://gist.github.com/nicman23/a2a80200e1d5d1f65b75846c5f2387b7) 此处]找到一个根据当前默认 PulseAudio 输出端自动执行该操作的脚本。
回声消除
PipeWire 可以实时从麦克风中去除扬声器的声音,这使得在不使用耳机的情况下参与语音聊天成为可能,甚至在其他应用程序正在播放音频时也是如此。
通常,语音聊天应用程序确实会消除回音,但它们只能识别经过自身的音频。例如,如果另一个语音聊天参与者通过你的扬声器讲话,聊天应用程序会“感知”到这一点,并能够选择性地从你的麦克风中擦除这种噪声,否则该噪声会作为烦人的回声被重复发送回语音聊天中。这种方法的弊端通常在于其他应用程序通过你的扬声器播放音频时——因为语音聊天无法感知这些音频,其他参与者可能会听到并抱怨。示例如下:
- 在使用独立语音聊天应用程序的同时玩网络视频游戏
- 在使用语音聊天应用程序的同时使用同步视频播放解决方案,例如 Jellyfin SyncPlay 或 Watch2Gether
这就是系统级回声消除所要解决的问题;你不再让语音聊天应用去抑制回声(且在上述情况下会失效),而是让 PipeWire 来完成,它能够天然地“感知”扬声器上播放的所有音频。
假设 PipeWire 采用默认配置,可以通过在 /etc/pipewire/pipewire.conf.d/ 中创建一个以“.conf”结尾且世界可读的配置文件来启用系统级回声消除,例如 60-echo-cancel.conf:
“aec.args”的默认值可以在 [[5](https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/plugins/aec/aec-webrtc.cpp) 此处]找到,只需在 “aec-webrtc.cpp” 中搜索 “webrtc.” 即可。
context.modules = [
# 回声消除
{ name = libpipewire-module-echo-cancel
args = {
# 监听模式:PipeWire 的回声消除模块不创建所有应用程序都必须播放的虚拟输出端,
# 而是直接从当前的默认(fallback)音频输出读取需要消除的音频
monitor.mode = true
capture.props = {
node.name = "ec_capture"
node.description = "Echo-cancellation capture"
media.name = "Echo-cancellation capture"
# 要从中消除回声的音频源/麦克风
# 可以通过以下命令查找:pw-cli list-objects Node | grep node.name
# 可选;如果未指定,模块将使用/跟随默认音频源
#node.target = "alsa_input.usb-UGREEN_Camera_UGREEN_Camera_SN0001-02.analog-stereo"
# 被动节点:当此捕获空闲时,不要使麦克风保持活动状态
node.passive = true
# 强制图中的输入流量子(quantum)
# 如果遇到语音失真/爆音,可以调整此项
# 默认:0/未设置
#node.force-quantum = 256
}
# 要从输入中过滤掉的输出端
# 可以通过以下命令查找:pw-cli list-objects Node | grep node.name
# 可选;如果未指定,模块将使用/跟随默认音频源
#sink.props = {
# node.target = "alsa_output.pci-0000_0f_00.4.analog-stereo"
#}
source.props = {
# 提供消除回声后的麦克风音频的虚拟音频源
node.name = "source_ec"
node.description = "Echo-cancellation source"
media.name = "Echo-cancellation source"
}
aec.args = {
# WebRTC 回声消除引擎的设置
# 增益控制:动态麦克风音频归一化
# 默认:false
# 注意,PipeWire WebRTC 源代码建议不要启用它:
# > 注意:AGC 似乎会干扰无关延迟检测(Agnostic Delay Detection),特别是
# > 对于语音,会导致非常差的性能,默认禁用
#webrtc.gain_control = true
# 扩展滤波器:放宽音频延迟窗口(?)
# 默认:true
# 引用已废弃的 Mozilla Positron 项目(2016年)的旧源码:
# > 扩展滤波器模式使我们能够灵活地忽略系统报告的延迟。
# > 我们对那些我们认为其提供结果与 AEC 期望不符的平台这样做。
# 建议:除非必要,否则将其关闭
webrtc.extended_filter = false
}
}
}
]
诸如此类的配置更改需要 重启 PipeWire(即 pipewire.service 和 pipewire-pulse.service 用户单元)才能生效。
语音处理
通过在滤波器链(filter chain)模块中使用均衡器(EQ)、高通滤波器、除嘶声器(de-esser)和压缩器,可以提高电容麦克风的语音清晰度。
安装 lsp-plugins-lv2包 和 calf包。在 ~/.config/pipewire/pipewire.conf.d/ 中创建一个 .conf 文件(或者修改你存放滤波器链模块的配置文件),使用以下模板:
context.modules = [{
name = libpipewire-module-filter-chain
args = {
filter.graph = {
nodes = [
# 高通滤波器 - 消除约 80Hz 以下的喷麦低频杂音
{
type = builtin
name = hp
label = bq_highpass
control = {
"Freq" = 90
"Q" = 0.707
}
}
# 除嘶声器 (LSP)
{
type = lv2
plugin = "[6](http://calf.sourceforge.net/plugins/Deesser)"
name = deesser
control = {
detection = 0 # 0=RMS, 1=Peak
mode = 1 # 0=Wide, 1=Split
threshold = 0.18 # ~ -15 dB 线性值
ratio = 4.0
laxity = 15
f1_freq = 6500.0 # "Split" 分频点
f2_freq = 6500.0 # "Peak" 检测中心
}
}
# 语音均衡器 (Calf)
{
type = lv2
plugin = "[7](http://calf.sourceforge.net/plugins/Equalizer5Band)"
name = eq
control = {
ls_active = 1
ls_freq = 100.0
ls_level = 0.668 # -3 dB,减少低频浑浊感
p1_active = 1
p1_freq = 300.0
p1_q = 1.0
p1_level = 0.794 # -2 dB,减少箱声/盒音
p2_active = 1
p2_freq = 3000.0
p2_q = 1.0
p2_level = 1.259 # +2 dB,增加临场感
hs_active = 1
hs_freq = 9000.0
hs_level = 0.708 # -3 dB,修饰高频刺耳声
}
}
# 压缩器 (Calf)
{
type = lv2
plugin = "[8](http://calf.sourceforge.net/plugins/Compressor)"
name = comp
control = {
threshold = 0.125 # ~ -18 dB 线性值
ratio = 3.0
attack = 10.0
release = 100.0
makeup = 1.585 # ~ +4 dB 线性值
}
}
]
# 这可以与 PipeWire wiki 页面上详细介绍的降噪插件结合使用,
# 只需取消下面 links[] 行的注释并更改输入即可
links = [
#{ output = "rnnoise:Output" input = "gate:Input" }
#{ output = "gate:Output" input = "hp:In" }
{ output = "hp:Out" input = "deesser:in_l" }
{ output = "deesser:out_l" input = "eq:in_l" }
{ output = "eq:out_l" input = "comp:in_l" }
]
#inputs = [ "rnnoise:Input" ]
inputs = [ "hp:In" ]
outputs = [ "comp:out_l" ]
}
capture.props = {
node.name = "capture.voice_processing"
node.description = "Voice processing capture"
media.name = "Voice processing capture"
node.passive = true
audio.rate = 48000
audio.position = [ FL ]
# 如果与上面的降噪示例结合使用,请使用
# node.target 来指定输入,而不是使用定位到输入默认源的 stream.capture.sink
#node.target = "source_ec"
stream.capture.sink = true
}
playback.props = {
node.name = "voice_processing_source"
node.description = "Voice processing source"
media.name = "Voice processing source"
media.class = "Audio/Source"
audio.rate = 48000
audio.position = [ FL ]
}
}
}]
要调整各种插件的数值,请使用电压/振幅分贝(dB)公式:linear = 10^(dB / 20)
将附加音频混入麦克风音频中
上面的回声消除示例可以进行扩展,以提供一个将音频复制到你的麦克风中的虚拟输出端。
这是对 PulseAudio/Examples#Mixing additional audio into the microphone's audio 的重新实现,并解决了相同的用例。
为实现这一点,你需要额外加载两个“Combine stream”(合并流)模块实例,如下所示。
目前,在每次重启或 PipeWire 重启后,该设置[[9](https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3710) 需要用户在例如 Helvum 中手动操作]才能完成;请参阅配置示例中的 “TODO” 注释。
context.modules = [
# (系统级回声消除配置,见上文)
# 音频效果输出端(立体声)
{ name = libpipewire-module-combine-stream
args = {
combine.mode = sink
node.name = sink_fx
node.description = "Effects sink (play shared audio here)"
combine.props = {
audio.position = [ FL FR ]
}
stream.props = {
# 如果你在 client.conf.d 中有上混音配置,请在此处设置相同的
# 参数,否则你的音效应用程序将不会在本地音频输出中被上混音
#channelmix.upmix = true # (...)
# 可能的替代方案:穷人的立体声上混音,即把前置声道镜像到后置扬声器
#combine.audio.position = [ FL FR FL FR ]
#audio.position = [ FL FR RL RR ]
}
}
}
# 主源(Main source)
# 提供将以下音源混合在一起的虚拟源:
# - source_ec(回声消除后的源)
# - sink_fx.monitor(音频效果输出端的监听器)
{ name = libpipewire-module-combine-stream
args = {
combine.mode = source
node.name = source_main
node.description = "Main source (record from here)"
#combine.latency-compensate = false
combine.props = {
audio.position = [ FL FR ]
}
stream.rules = [
{ matches = [
{
node.name = "source_ec"
media.class = "Audio/Source"
}
]
actions = {
create-stream {
}
}
}
# TODO 包含匹配和动作的块,用于匹配 sink_fx 的监听器并将其连接到 source_main
# 目前尚无自动化此操作的已知 PipeWire 配置
# 有关最新消息,请参阅此 PipeWire 问题:
# [10](https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3710)
# 目前,请在 Helvum 中手动添加所需的连接,即连接这些点:
# - sink_fx.monitor_FL -> source_main.output.input_FL
# - sink_fx.monitor_FR -> source_main.output.input_FR
]
}
}
]
将音频流传输到 Android 设备
可在 Android 上使用的 [[11](https://github.com/kaytat/SimpleProtocolPlayer) Simple Protocol Player] 应用程序可以通过 [[12](https://docs.pipewire.org/page_module_protocol_simple.html) PipeWire 简单协议]接收音频流。这允许将主机上的音频播放通过 TCP 路由到 Android 设备。
创建一个 PipeWire 配置文件 ~/.config/pipewire/pipewire.conf.d/android-streaming.conf,内容如下:
context.modules = [
{
name = libpipewire-module-protocol-simple
args = {
# 启用捕获模式(向外发送音频)
capture = true
playback = false
# 音频格式设置
audio.rate = 48000
audio.channels = 2
# TCP 地址和端口
# 替换为 Android 设备的 IP 地址
server.address = [
"tcp:12345"
]
capture.props = {
# 替换为你的输出设备名称
# 可通过以下命令查找:pw-cli list-objects Node | grep node.name
target.object = "alsa_output.usb-Creative_Technology_Ltd_SB_X-Fi_Surround_5.1_Pro_0000007E-00.analog-stereo"
stream.capture.sink = true
}
}
}
]
重启 PipeWire 以应用配置。在 Android 设备上,打开 Simple Protocol Player 应用并连接到配置端口上的主机 IP 地址。