跳至內容

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 連接是未加密且未經身份驗證的。請僅在受信任的網絡上使用此功能。