跳转到内容

PipeWire/示例

来自 Arch Linux 中文维基

环绕声系统

分离前置与后置声道

当使用 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 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 替换为你声卡的名称。在脚本执行前添加延迟也可能会使运行更加平稳。
注意:Jack/Pipewire 的输出端名称似乎不时会发生变化。在上面的示例中,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 地址。

注意:TCP 连接是未加密且未经身份验证的。请仅在受信任的网络上使用此功能。