PipeWire/Examples - ArchWiki

Jump to content

# PipeWire/Examples

From ArchWiki
< PipeWire

Related articles

- PipeWire
- PulseAudio/Examples

## Surround sound systems

### Splitting front/rear

When using PipeWire as a PulseAudio/JACK replacement, you can set up PipeWire to replicate the PulseAudio example for splitting front/rear. Doing this allows you to send audio streams using PulseAudio to a separate sink for speakers or headphones.

Connect speakers to the line-out port and headphones to the rear port. In `pavucontrol` set the soundcard used to Analog Surround 4.0 Output.

#### Using a PipeWire config file

Create an additional PipeWire config file `~/.config/pipewire/pipewire.conf.d/speakers-headphones.conf` or `/etc/pipewire/pipewire.conf.d/speakers-headphones.conf` with the following content and adapt the `target.object` lines:

    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 ]
                }
            }
        }
    ]

Restart PipeWire and you should see two new outputs in `pavucontrol`. Similar examples can be found in the PipeWire Loopback module documentation

#### Using `pactl` and `pw-link`

Using the following commands, make new sinks for the speakers and for the headphones, link the speakers to the front channels and link the headphones to the rear channels:

     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` keeps the sinks alive after the creating client disconnects. You can name `sink_name` whatever you want.

In order to unload module, you can use `pw-cli destroy ID`, where `ID` is output of `pactl load-module` command. Unloading individual modules through `pactl unload-module` is not currently supported [1]. However, you may use it to unload all `module-null-sink` modules using `pactl unload-module module-null-sink`.

Using `jack_connect`, connect the monitors of the new sinks to the sound card's playback ports. Find out the name of the channels by running `pw-link -iol`[2].

     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

Tip Add the above commands to a script and autostart it to automate the process. Be sure to replace `alsa_output.pci-0000_00_14.2.analog-surround-40` with the name of your sound card. Adding a delay before the script executes may also make things work smoother.
Note The Jack/Pipewire sinks seem to change name now and then. In the above example, `alsa_output.pci-0000_00_14.2.analog-surround-40:playback_FL` sometimes changes into `Built-in\ Audio\ Analog\ Surround\ 4.0:playback_FL`. As a quick workaround, you could add a second set of `pw-jack` commands in your autostart script with the second set of names.

To individually control the volumes, one option is to use ALSA utilities—such as amixer(1)—to control Front and Rear/Surround (ALSA naming) channels. A script to automatically do that depending on what is your currently default PulseAudio sink can be found here.

## Echo cancellation

PipeWire can remove your speakers' sounds from your microphone in real time, which makes it possible to attend audio chats without having to use headphones, even while other applications are playing audio.

Usually, voice chat applications do cancel out feedback, but they are only aware of audio that goes through them. As an example, if another voice chat attendant talks on your speakers, the chat application "knows" about it and is able to selectively erase this noise from your microphone, which would otherwise be repeated back into the voice chat as an annoying echo. The problems with this approach tend to start when other applications are playing to your speakers, because this audio the voice chat does not know about, and the other participants may hear it and complain. Example situations:

- Playing an online video game while using a separate voice chat application
- Using a synchronized video playback solution, such as Jellyfin SyncPlay or Watch2Gether, while using a voice chat application

This is the problem that system-wide echo cancellation solves; instead of having the voice chat app suppress the echo – and fail in the above situations – you make PipeWire do that, which innately "knows about" all audio that is played on the speakers.

Assuming a blank PipeWire configuration, system-wide echo cancellation can be enabled by creating a world-readable configuration file in `/etc/pipewire/pipewire.conf.d/` whose name ends with ".conf", for example `60-echo-cancel.conf`:

Default values for "aec.args" can be found here, just search for "webrtc." in the "aec-webrtc.cpp".

     context.modules = [

         # Echo cancellation
         {   name = libpipewire-module-echo-cancel
             args = {
                 # Monitor mode: Instead of creating a virtual sink into which all
                 # applications must play, in PipeWire the echo cancellation module can read
                 # the audio that should be cancelled directly from the current fallback
                 # audio output
                 monitor.mode = true
                 capture.props = {
                     node.name = "ec_capture"
                     node.description = "Echo-cancellation capture"
                     media.name = "Echo-cancellation capture"
                     # The audio source / microphone the echo should be cancelled from
                     # Can be found with: pw-cli list-objects Node | grep node.name
                     # Optional; if not specified the module uses/follows the fallback audio source
                     #node.target = "alsa_input.usb-UGREEN_Camera_UGREEN_Camera_SN0001-02.analog-stereo"
                     # Passive node: Do not keep the microphone alive when this capture is idle
                     node.passive = true
                     # Force quantum of input stream in the graph
                     # Fiddle with if experiencing voice distortion/crackling
                     # Default: 0/unset
                     #node.force-quantum = 256
                 }
                 # Output sink to be filtered from input
                 # Can be found with: pw-cli list-objects Node | grep node.name
                 # Optional; if not specified the module uses/follows the fallback audio source
                 #sink.props = {
                 #   node.target = "alsa_output.pci-0000_0f_00.4.analog-stereo"
                 #}
                 source.props = {
                     # The virtual audio source that provides the echo-cancelled microphone audio
                     node.name = "source_ec"
                     node.description = "Echo-cancellation source"
                     media.name = "Echo-cancellation source"
                 }
                 aec.args = {
                     # Settings for the WebRTC echo cancellation engine
                     # Gain control: On-the-fly microphone audio normalization
                     # Default: false
                     # Caution, the PipeWire WebRTC source code advises against enabling it:
                     #  > Note: AGC seems to mess up with Agnostic Delay Detection, especially
                     #  > with speech, result in very poor performance, disable by default
                     #webrtc.gain_control = true
                     # Extended filter: Widened audio delay window (?)
                     # Default: true
                     # Quote from the old source of the abandoned Mozilla Positron project (2016):
                     #  > The extended filter mode gives us the flexibility to ignore the system's
                     #  > reported delays. We do this for platforms which we believe provide results
                     #  > which are incompatible with the AEC's expectations.
                     # Suggestion: Turn it off unless required
                     webrtc.extended_filter = false
                 }
             }
         }
     ]

Configuration changes such as these require a PipeWire restart (i.e. the `pipewire.service` and `pipewire-pulse.service` user unit) to become effective.

## Voice processing

The filter chain module may be used to improve vocal clarity in condenser microphones by employing an EQ, high-pass filter, de-esser, and compressor in the filter chain module.

Install lsp-plugins-lv2 and calf. Create a `.conf` file in `~/.config/pipewire/pipewire.conf.d/` (or modify the one where your filter chain module lives,) making use of the following template:

     context.modules = [{
       name = libpipewire-module-filter-chain
       args = {
           filter.graph = {
               nodes = [
                   # High-pass filter - kills plosive rumble below ~80Hz
                   {
                       type   = builtin
                       name   = hp
                       label  = bq_highpass
                       control = {
                           "Freq"  = 90
                           "Q"     = 0.707
                       }
                   }
                   # De-esser (LSP)
                   {
                       type    = lv2
                       plugin  = "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 linear
                           ratio     = 4.0
                           laxity    = 15
                           f1_freq   = 6500.0   # "Split" crossover
                           f2_freq   = 6500.0   # "Peak" detection center
                       }
                   }
                   # Voice EQ (Calf)
                   {
                       type    = lv2
                       plugin  = "http://calf.sourceforge.net/plugins/Equalizer5Band"
                       name    = eq
                       control = {
                           ls_active = 1
                           ls_freq   = 100.0
                           ls_level  = 0.668    # -3 dB, reduce mud
                           p1_active = 1
                           p1_freq   = 300.0
                           p1_q      = 1.0
                           p1_level  = 0.794    # -2 dB, boxiness
                           p2_active = 1
                           p2_freq   = 3000.0
                           p2_q      = 1.0
                           p2_level  = 1.259    # +2 dB, presence
                           hs_active = 1
                           hs_freq   = 9000.0
                           hs_level  = 0.708    # -3 dB, tame harshness
                       }
                   }
                   # Compressor (Calf)
                   {
                       type    = lv2
                       plugin  = "http://calf.sourceforge.net/plugins/Compressor"
                       name    = comp
                       control = {
                           threshold = 0.125    # ~ -18 dB linear
                           ratio     = 3.0
                           attack    = 10.0
                           release   = 100.0
                           makeup    = 1.585    # ~ +4 dB linear
                       }
                   }
               ]
               # this can be combined with the noise suppression plugin detailed
               # on the PipeWire wiki page by uncommenting the links[] lines below
               # and changing the input
               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 ]
               # if combining with the noise suppression example above, use
               # node.target to specify the input rather than stream.capture.sink
               # which targets the input fallback
               #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 ]
           }
       }
     }]

To tweak the values of the various plugins, use the voltage/amplitude dB formula: `linear = 10^(dB / 20)`

## Mixing additional audio into the microphone's audio

The echo cancellation example above can be extended to provide a virtual sink that copies audio into your microphone.

It is a re-creation of PulseAudio/Examples#Mixing additional audio into the microphone's audio and solves the same use-case.

To achieve this you additionally load two instances of the "Combine stream" module, as shown below.

Currently, after each reboot or PipeWire restart the setup requires manual user action in e.g. Helvum to complete it; see the "TODO" comment in the configuration example.

     context.modules = [

         # (Configuration for system-wide echo cancellation, see above)

         # Audio effects sink (stereo)
         {   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 = {
                     # If you have an upmix configuration in client.conf.d, set the same
                     # parameters here, or else your sound effects application will not
                     # be upmixed in your local audio output
                     #channelmix.upmix = true  # (...)
                     # Possible alternative: Poor man's stereo upmix, i.e. mirroring front
                     # to rear speakers
                     #combine.audio.position = [ FL FR FL FR ]
                     #audio.position = [ FL FR RL RR ]
                 }
             }
         }

         # Main source
         # Virtual source that supplies these sources mixed together:
         #  - source_ec (Echo-cancelled source)
         #  - sink_fx.monitor (Monitor of the audio effects sink)
         {   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 Block with matches= and actions= that matches the monitor of
                     # sink_fx and hooks it up to source_main
                     # No PipeWire configuration known yet that automates this
                     # See this PipeWire issue for news:
                     # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3710
                     # For the time being, add the required connections manually in Helvum,
                     # i.e. connect these points:
                     #  - sink_fx.monitor_FL -> source_main.output.input_FL
                     #  - sink_fx.monitor_FR -> source_main.output.input_FR
                 ]
             }
         }
     ]

## Streaming audio to Android devices

The Simple Protocol Player app available on Android can receive audio streams via the PipeWire Simple Protocol. This allows routing audio playback from the host to an Android device over TCP.

Create a PipeWire configuration file `~/.config/pipewire/pipewire.conf.d/android-streaming.conf` with the following content:

     context.modules = [
         {
             name = libpipewire-module-protocol-simple
             args = {
                 # Enable capture mode (sends audio out)
                 capture = true
                 playback = false

                 # Audio format settings
                 audio.rate = 48000
                 audio.channels = 2

                 # TCP address and port
                 # Replace with the Android device's IP address
                 server.address = [
                     "tcp:12345"
                 ]

                 capture.props = {
                     # Replace with your output device name
                     # Find it with: 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
                 }
             }
         }
     ]

Restart PipeWire to apply the configuration. On the Android device, open the Simple Protocol Player app and connect to the host's IP address on the configured port.

Note The TCP connection is unencrypted and unauthenticated. Only use this on trusted networks.
Category:

- Multimedia

PipeWire/Examples
Add topic