WirePlumber

From ArchWiki

WirePlumber is a powerful session and policy manager for PipeWire. Based on a modular design, with Lua plugins that implement the actual management functionality, it is highly configurable and extendable.

Installation

Install the wireplumber package. It will conflict with other PipeWire Session Managers and make sure they are uninstalled.

WirePlumber uses systemd user units to manage the server.

Configuration

This article or section is out of date.

Reason: WirePlumber's configuration format is changing.[1] (Discuss in ArchWiki talk:Requests#Update for new WirePlumber configuration format)

Configuration file layout

WirePlumber's configuration is comprised of global PipeWire-flavored JSON objects such as context and alsa_monitor that are modified to change its behavior. The configuration files are read from ~/.config/wireplumber/ (user configuration), /etc/wireplumber/ (global configuration), and then /usr/share/wireplumber/ (stock configuration).

WirePlumber starts by reading the main configuration file. This is a JSON-like file that sets up the PipeWire context, SPA plugins, modules, and components. Among these components is the Lua scripting engine, which is used to dynamically modify the global objects.

There are different main configuration files that ship with the package:

  • The single-instance configuration file, at /usr/share/wireplumber/wireplumber.conf. This is the default configuration, which includes the functionality of all the other configurations within one process.
  • The main configuration file, at /usr/share/wireplumber/main.conf. This loads the modules and components necessary for the WirePlumber core, and loads Lua configuration files[dead link 2024-01-13 ⓘ] from config-dir/main.lua.d/. [2][dead link 2024-01-13 ⓘ]
  • The Bluetooth configuration file, at /usr/share/wireplumber/bluetooth.conf. This is suitable for a WirePlumber process that is handling Bluetooth connectivity for the core process. This loads Lua files from config-dir/bluetooth.lua.d/.
  • The policy configuration file, at /usr/share/wireplumber/policy.conf. This encapsulates policy functionality, which is how WirePlumber makes decisions about moving and making changes to nodes. This loads Lua files from config-dir/policy.lua.d/

The Lua configuration files in the lua.d/ directories also load Lua scripts from config-dir/scripts/. These scripts implement some of the logic/functionality of Pipewire, and could also be worth modifying under certain circumstances.

Modifying the configuration

The recommended way to configure WirePlumber is to add a Lua script to the appropriate lua.d/ directory within /etc/wireplumber/ or ~/.config/wireplumber/. Some things to consider are:

  • If there is an existing script you want to override, copy it from /usr/share/wireplumber/ to the destination while keeping its name the same. Configuration files with the same name but in a lower priority location will be ignored. [3][dead link 2024-01-13 ⓘ]
  • Otherwise, if you are adding a new script, you should start it with a number bigger than 50 (e.g. 51-my-config.lua), as the default configuration is mostly done at or below 50 in the alphanumeric ordering.
    • Note that WirePlumber performs multi-path merging[dead link 2024-01-13 ⓘ], meaning that a lower numbered stock config will run before your new script, as alphanumeric ordering takes precedence over directory priorities.
  • The directory you choose should be consistent with what the stock configuration does, but it does not strictly matter unless you are using multiple instances[dead link 2024-01-13 ⓘ].
  • ALSA properties for Bluetooth devices must be configured in bluetooth.lua.d/. An ALSA property for a Bluetooth device set in main.lua.d/ is not applied.

Obtain interface name for rules matching

In the Lua scripts, you need to specify matches rules with a property from an object of the interface you want to configure.

Use the command wpctl status to show all objects managed by WirePlumber. Find the ID assigned to the target interface. Then use command wpctl inspect ID to get a needed property.

Example, if you target interface is HD Audio Controller Analog Stereo and consider following output:

$ wpctl status
PipeWire 'pipewire-0' [0.3.56, user@hostname, cookie:1163266174]

Audio
 ├─ Devices:
 │      42. HD Audio Controller                 [alsa]
 │     105. USB PnP Audio Device                [alsa]
 │
 ├─ Sinks:
 │  *   48. HD Audio Controller Analog Stereo   [vol: 0.50]
 │
 ├─ ...
 │
 ├─ Sources:
 │  *  101. USB PnP Audio Device Mono           [vol: 0.74]
 │
 └─ ...

the interface ID is 48.

Then use inspect command to view object detail, listed all properties in that object:

$ wpctl inspect 48
id 48, type PipeWire:Interface:Node
    ...
    device.api = "alsa"
    device.class = "sound"
  * device.id = "42"
    device.profile.description = "Analog Stereo"
    device.profile.name = "analog-stereo"
    ...
  * factory.id = "18"
    factory.mode = "merge"
    factory.name = "api.alsa.pcm.sink"
    ...
  * media.class = "Audio/Sink"
  * node.description = "HD Audio Controller Analog Stereo"
  * node.name = "alsa_output.pci-0000_08_00.4.analog-stereo"
  * node.nick = "ALC1220 Analog"
    ...
  * object.path = "alsa:pcm:1:front:1:playback"
  * object.serial = "49"
    ...

Choose device.name or node.name property to use with the matches rules in the Lua configuration script.

Avoid using device.id, it is dynamic and changes often.

Multiple properties in matches rules is possible, see the alsa_monitor.rules section in documentation for the WirePlumber ALSA configuration.

Note:
  • You can determine the Endpoint class of this object from the media.class property.
  • For ALSA, node objects are Sinks or Sources / device objects correspond to the card.
  • Since v0.4.9, ALSA nodes use the PCM name to populate node.nick, which is useful at least on HDA cards using UCM, where all outputs (analog, HDMI, etc.) are exposed as Node on a single profile.
Tip:
  • The command pw-top shows the PipeWire Device and Node currently in use.
  • Similar inspection command from PipeWire is pw-cli info ID.

Changing a device/node property

To change a device or node property, such as its description or nick, you must create a Lua script and add it into /etc/wireplumber/ or ~/.config/wireplumber/ under the proper path and name.

For instance, to change the description of an ALSA node, you would create a file such as:

/etc/wireplumber/main.lua.d/51-device-rename.lua (or ~/.config/wireplumber/main.lua.d/51-device-rename.lua)
rule = {
  matches = {
    {
      { "node.name", "equals", "alsa_output.pci-0000_00_1f.3.output_analog-stereo" },
    },
  },
  apply_properties = {
    ["node.description"] = "Laptop",
  },
}

table.insert(alsa_monitor.rules, rule)

If instead you wish to change something on a Bluetooth node or device, you could create a file such as:

/etc/wireplumber/bluetooth.lua.d/51-device-rename.lua (or ~/.config/wireplumber/bluetooth.lua.d/51-device-rename.lua)
rule = {
  matches = {
    {
      { "node.name", "equals", "bluez_output.02_11_45_A0_B3_27.a2dp-sink" },
    },
  },
  apply_properties = {
    ["node.nick"] = "Headphones",
  },
}

table.insert(bluez_monitor.rules, rule)

The properties that you can change as well as the matching rules to select devices or nodes are documented at ALSA configuration and Bluetooth configuration.

Disable a device/node

Since WirePlumber v0.4.7, users could now disable any devices or nodes by property device.disabled or node.disabled:

/etc/wireplumber/main.lua.d/51-alsa-disable.lua (or ~/.config/wireplumber/main.lua.d/51-alsa-disable.lua)
rule = {
  matches = {
    {
      { "device.name", "equals", "alsa_card.pci-0000_08_00.4" },
    },
  },
  apply_properties = {
    ["device.disabled"] = true,
  },
}

table.insert(alsa_monitor.rules,rule)

For the name of alsa_card.* in your system, see #Obtain interface name for rules matching.

Note: Common use-case, for example, is to disable NVIDIA's HDMI audio output.

Simultaneous output to multiple sinks on the same sound card

Create a copy of /usr/share/alsa-card-profile/mixer/profile-sets/default.conf so that changes persist across updates. Here we define a profile joining the two default mappings for Analog and HDMI.

/usr/share/alsa-card-profile/mixer/profile-sets/multiple.conf
[General]
auto-profiles = no

[Mapping analog-stereo]
device-strings = front:%f
channel-map = left,right
paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
priority = 15

[Mapping hdmi-stereo]
description = Digital Stereo (HDMI)
device-strings = hdmi:%f
paths-output = hdmi-output-0
channel-map = left,right
priority = 9
direction = output

[Profile multiple]
description = Analog Stereo Duplex + Digital Stereo (HDMI) Output
output-mappings = analog-stereo hdmi-stereo
input-mappings = analog-stereo

Now we configure Wireplumber to use the new card-profile for matching devices. For identifying information see #Obtain interface name for rules matching. We apply the configuration by creating a Lua script such as the following:

/etc/wireplumber/main.lua.d/51-alsa-custom.lua (or ~/.config/wireplumber/main.lua.d/51-alsa-custom.lua)
rule = {
  matches = {
    {
      { "device.nick", "matches", "HDA Intel PCH" },
    },
  },
  apply_properties = {
    ["api.alsa.use-acp"] = true,
    ["api.acp.auto-profile"] = false,
    ["api.acp.auto-port"] = false,
    ["device.profile-set"] = "multiple.conf",
    ["device.profile"] = "multiple",
  },
}
table.insert(alsa_monitor.rules,rule)

Simultaneous output to transient devices

You may wish to output sound to onboard and external devices simultaneously - even when the external devices are not always plugged in. To accomplish this we create a virtual node that will always be present, regardless of what hardware is plugged in. We then link the transient hardware (in this example USB headphones) to the virtual node whenever they are plugged in.

First create a script that will run during login (this is usually easiest done via your window manager's Startup function).

/usr/local/sbin/create-virtual-sink.sh
#!/bin/bash

# Create a new sink called Simultaneous Output
pw-cli create-node adapter '{ factory.name=support.null-audio-sink node.name="Simultaneous Output" node.description="Simultaneous Output" media.class=Audio/Sink object.linger=true audio.position=[FL FR] }'

# Connect the normal permanent sound card output to the new sink
pw-link "Simultaneous Output:monitor_FL" alsa_output.pci-0000_05_04.0.analog-stereo:playback_FL
pw-link "Simultaneous Output:monitor_FR" alsa_output.pci-0000_05_04.0.analog-stereo:playback_FR

# Switch the default output to the new virtual sink
wpctl set-default `wpctl status | grep "\. Simultaneous Output" | egrep '^ │( )*[0-9]*' -o | cut -c6-55 | egrep -o '[0-9]*'`

In the above example, initially the only output device is our 'normal' on-board soundcard (alsa_output.pci-0000_05_04.0.analog-stereo). You can find the designator for your card by running wpctl status and wpctl inspect.

Run the following script when your USB headphones are inserted in order to link them to the virtual sink:

link-virtual-sink-headphones.sh
#!/bin/bash
# link the virtual sync to your headphones should run when detected by UDEV

# wait a second for the drivers to load
sleep 1s

# link the headphones to your virtual sink
sudo -u user1 env XDG_RUNTIME_DIR=/run/user/1000 pw-link "Simultaneous Output:monitor_FL" alsa_output.usb-Kingston_HyperX_Cloud_Flight_S_000000000001-00.analog-stereo:playback_FL
sudo -u user1 env XDG_RUNTIME_DIR=/run/user/1000 pw-link "Simultaneous Output:monitor_FR" alsa_output.usb-Kingston_HyperX_Cloud_Flight_S_000000000001-00.analog-stereo:playback_FR

# finish and return the code for success
exit 0

Ideally you would run this script automatically when your headphones are inserted. The instructions on the Udev page describe how you would create a custom rule for that. (Although note, that you cannot run this script directly - because UDEV will not load drivers until _after_ any specifed script has run. So you will have to have an intermediate script with some nohup trickery or something like that). You will also need to modify the above script so that the XDG_RUNTIME_DIR matches your user id number and user1 will need to be replaced with your username.

You can add any arbitrary number of devices to this virtual sink in the same manner.

If you are having trouble keeping track what devices are connected where, the qpwgraph tool is excellent for getting a visual representation of which devices are connected to each other.

Tips and tricks

Keyboard volume control

See Keyboard shortcuts#Xorg to bind the following commands to your volume keys: XF86AudioRaiseVolume, XF86AudioLowerVolume, XF86AudioMute and XF86AudioMicMute.

To raise the volume, with a limit of 150%:

$ wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+

To lower the volume:

$ wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-

To mute/unmute the volume:

$ wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle

To mute/unmute the microphone:

$ wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
Tip: To use sinks or sources other than the default ones, run wpctl status to list all available sinks, and use sink or source numbers in place of @DEFAULT_AUDIO_SINK@ or _SOURCE@.

Show volume level

To get the volume level and mute status of the default sink:

$ wpctl get-volume @DEFAULT_AUDIO_SINK@
Volume: 0.45
Note: The mute status is only visible when the sink is muted.

Keep Bluetooth running after logout / Headless Bluetooth

By default /usr/share/wireplumber/bluetooth.lua.d/50-bluez-config.lua enables the logind module which disables Bluetooth whenever the session is terminated. You can override this like so:

/etc/wireplumber/bluetooth.lua.d/50-bluez-config.lua (or ~/.config/wireplumber/bluetooth.lua.d/50-bluez-config.lua)
bluez_monitor.properties = {
  ["with-logind"] = false,
}
Note: You also need to enable lingering for the user running PipeWire/Wireplumber.

For development versions of wireplumber-gitAUR (since 0.4.81), the requirement to support.logind has to be commented out for the bluez seat-monitoring.

/usr/share/pipewire/wireplumber.conf
...
   {
     type = virtual, provides = monitor.bluez.seat-monitoring,
   # requires = [ support.logind ]
   }
...

See also