What if you want to have your phone’s alarm clock volume different from your music playback volume and have the later go to speakers while alarms should continue to go to the phone’s speaker?

While this could be handled manually via per application volume and sink setups, this doesn’t scale well on phones that also have emergency alerts, incoming calls, voice assistants, etc. It also doesn’t specify how to handle simultaneous playback - for instance, if an incoming call rings while music is playing.

The modern Linux audio stack uses pipewire and wireplumber and the wireplumber maintainers implemented a solution for this called role based routing. In this post we’ll explore how this works, what is already implemented and what we need to do so we can enable it by default in Phosh.

Role Based Policies Link to heading

The role based policy is a wireplumber configuration telling wireplumber which roles exist, which priorities they have and how they behave in relation to each other.

In the context of phones typical roles are Music, Notification, Alarm or Alert and the policy then specifies if e.g. a stream of a certain role should be totally muted when another one is active (this is called corking) or if it should merely be less loud (called ducking). The decision is based on the roles priority. Wireplumber’s example policy for this can be found here.

Here’s a snippet for the alarm role:

  {
    name = libpipewire-module-loopback, type = pw-module
    arguments = {
      # Alarm clocks
      node.name = "loopback.sink.role.alarm"
      node.description = "Alarm Clocks"
      capture.props = {
        device.intended-roles = [ "Alarm" ]
        device.icon-name = "alarm"
        policy.role-based.priority = 30
        policy.role-based.action.same-priority = "mix"
        policy.role-based.action.lower-priority = "duck"
        policy.role-based.preferred-target = "Speaker"
      }
    }
    provides = loopback.sink.role.alarm
  }

While ducking and corking is not new and is being leveraged with pulseaudio to e.g. silence music during phone calls on e.g. the Librem 5 already, the role based priorities in Wireplumber offer more flexibility to handle relationships between different stream types and allow one to access the properties of these streams (like their icon or current volume).

Media Roles Link to heading

In order to leverage this, applications tag their audio streams with a certain role so wireplumber can apply the corking and ducking rules based on the assigned priorities. E.g. applications using libpulse to play audio can set the role by using the PA_PROP_MEDIA_ROLE property.

In Phosh event sounds for alarm clocks, cellbroadcast alerts or incoming calls are usually emitted by feedbackd. Hence applications using libfeedback don’t need to set any roles manually as feedbackd will set an appropriate role base on the event-name.

Roles aren’t yet standardized on Linux, so we researched existing usage, added the requirements for mobile Linux (which should be useful on desktop too) and proposed an addition to the xdg-spec:

Role Tags Comment
Accessibility A11y e.g. screen readers
Alarm Alarm e.g. alarm clocks
Alerts Alert, Emergency e.g. Cell Broadcast alerts
Game Game
Movie Movie
Multimedia Multimedia
Music Music
Navigation Navigation, GPS e.g. turn by turn instructions
Notifications Notification, event (used by pulseaudio) desktop notifications
Ringtones Ringtone e.g. ringtones of incoming audio/video calls
Voice Assistant Assistant
Voice Communication, Phone e.g. call audio

We then added the ones currently relevant to our proposed default configurattion.

If you want to try this out you can build feedbackd with the -Dmedia-roles option. meson install will then put a media-role-nodes.conf into /usr/share/wireplumber/wireplumber.conf.d/ enabling the media role based policy in Wireplumber. feedbackd will then also add media.role properties to the emitted events ensuring that wireplumber will silence any playing music or game output on e.g. incoming calls or emergency alerts.

How do check if role based routing is active? Link to heading

If role based routing is active, then wpctl status output should list the known media roles as filters:

 ├─ Filters:
 │    - loopback-65760-22
 │      36. input.loopback.sink.role.alert                               [Audio/Sink]
 │      38. output.loopback.sink.role.alert                              [Stream/Output/Audio]
 │    - loopback-65760-21
 │      40. input.loopback.sink.role.ringtone                            [Audio/Sink]
 │      41. output.loopback.sink.role.ringtone                           [Stream/Output/Audio]
 │    - loopback-65760-19
 │      45. input.loopback.sink.role.notification                        [Audio/Sink]
 │      55. output.loopback.sink.role.notification                       [Stream/Output/Audio]
 │    - loopback-65760-20
 │      49. output.loopback.sink.role.alarm                              [Stream/Output/Audio]
 │      75. input.loopback.sink.role.alarm                               [Audio/Sink]
 │    - loopback-65760-18
 │      52. output.loopback.sink.role.multimedia                         [Stream/Output/Audio]
 │      72. input.loopback.sink.role.multimedia                          [Audio/Sink]

You can inspect the properties from the configuration via wpctl inspect <id>:

$ wpctl inspect 36
id 36, type PipeWire:Interface:Node
    device.intended-roles = "[ "Alert", "Emergency" ]"
    policy.role-based.action.lower-priority = "cork"
    policy.role-based.action.same-priority = "mix"
    policy.role-based.preferred-target = "Speaker"
    policy.role-based.priority = "100"
    policy.role-based.target = "true"

Changing Volume Link to heading

So that’s it? Unfortunately no. What if you want to change the alarm or incoming call volume? For that we need separate volume sliders for the various roles so one can tune them individually and since the roles known to the system can be changed by the user we can’t hardcode this but need to build that dynamically. Since Phosh 0.47 this is handled in the mobile settings app. It basically does the same as our wpctl status above and looks for the relevant loopbacks:

Mobile Settings Volume controls

The Default Volume Control Link to heading

What also changes for the user is the notion of a the default volume control. One usually expects the phone’s volume buttons or main volume slider to change the volume of the currently playing stream. Imagine music playing a and phone call coming in, pressing volume down should then lower the volume of the ringing (remember that due to our policy the music is already corked at that point in time). For this to work we need to introduce the notion of a default volume which is the highest priority role that is currently active. We implemented this in wireplumber via this MR (which got recently merged) by setting a current.role-based.volume property which can then be queried:

pw-dump | grep current.role-based.volume
      { "subject": 0, "key": "current.role-based.volume.control", "type": "Spa:String:JSON", "value": { "name": "input.loopback.sink.role.multimedia" } }

When a call is incoming (which can be simulated e.g. via fbcli -E phone-incoming-call -t 30) this changes:

$ pw-dump | grep current.role-based.volume
      { "subject": 0, "key": "current.role-based.volume.control", "type": "Spa:String:JSON", "value": { "name": "input.loopback.sink.role.ringtone" } }

or when the alarm clock goes off (fbcli -E alarm-clock-elapsed -t 120):

$ pw-dump | grep current.role-based.volume
      { "subject": 0, "key": "current.role-based.volume.control", "type": "Spa:String:JSON", "value": { "name": "input.loopback.sink.role.alarm" } }

We can then use that in Phosh to make the default volume slider operate on this volume control. A prototype for this can be found here).

Routing Target Link to heading

With volume controls in place, we still need to address where the audio will output. Most mobile phones have a speaker, an earpiece, and possibly Bluetooth headphones connected. While Bluetooth headphones might be the preferred target for music, emergency alerts and alarms should ideally route to the phone’s speaker by default.

This is handled via the policy.role-based.preferred-target option in the role based policy as implemented here. In our default policy if a Speaker sink is found we route alarms and emergency alerts to it unless changed by the user. The sink node is matched by the node.name or node.nick properties. In the picture below you can see how alerts (and also alarms) are routed to the speaker sink.

Wireplumber routing

Here is the full graph showing how other streams go to the Bluetooth headphones.

Trying it out Link to heading

So how can you try that out? Until more things made it into releases we have to build some components from source (see below for released versions that make this obsolete):

  • feedbackd: The needed changes are merge so you can clone it an run from the working copy
git clone https://gitlab.freedesktop.org/feedbackd/feedbackd.git
cd feedbackd
# Need to enable `media-roles` support as they're off by default
meson setup -Dmedia-roles=true _build
meson compile -C _build
_build/run
  • wireplumber has the needed changes merged, so you can build it from the latest upstream.
git clone https://gitlab.freedesktop.org/pipewire/wireplumber.git
cd wireplumber
meson setup _build
meson compile -C _build
# Add our policy roles
wget -Osrc/config/wireplumber.conf.d/media-role-nodes.conf 'https://gitlab.freedesktop.org/feedbackd/feedbackd/-/raw/main/data/media-role-nodes.conf?ref_type=heads'
# Run wireplumber from the working copy
./wp-uninstalled.sh wireplumber
  • phosh-mobile-settings has support built in since 0.47, so no need to build anything.
  • phosh needs the merge request currently under development built:
git clone https://gitlab.gnome.org/World/Phosh/phosh.git
cd phosh
git fetch refs/merge-requests/1714/head
git checkout FETCH_HEAD
meson setup -Dtests=false -Dtools=false _build
meson compile -C _build
# This installs to /usr/local, you can remove it once done testing
# phosh-session will automatically pick it up
meson install -C _build

With this you should be able to check if the role based policies are active and e.g. see the volume slider in mobile settings.

If you can do without the GUI bits and want to avoid building lots of stuff manually but still want to try things out, it is sufficient to only build wireplumber and add the policy. You can then use pw-play to add media roles and test default volumes, ducking, corking and routing:

pw-play --media-role Alert  "/usr/share/sounds/phosh/stereo/video-incoming-call.oga"
Note 1
If you’re running wireplumber 0.5.13 or newer you can skip the above and only need to install media-role-nodes.conf into ~/.config/wireplumber/wireplumber.conf.d/ and restart wireplumber via `systemctl –user restart.
Note 2
If you’re running phosh 0.52~rc1 or newer there’s no need to build phosh to prevent the stream volumes from being shown. It’s still needed to track a suitable default volume though.

Next steps Link to heading

First step will be to enable the feedbackd side after more testing so the role based policies get enabled, giving us ducking and corking of streams and (after the wireplumber 0.5.13 release) routing to the preferred outputs (e.g. alarms to the speaker). We then have some bits left, we need to

  • fix this pipewire bug currently preventing that the volumes are being restored before a stream is played leading to odd UI (Update: This is fixed in pipewire)
  • handle call audio volume controls for cases where it is not a pipewire stream
  • finish and land the above mentioned Phosh side merge request so the volume slider and the volume rockers adjust the correct volume

If you want to follow along: we track the status at here. This is also a gentle reminder that if you’re a vendor shipping Phosh and aren’t using pipewire for audio yet it might be good to look into this to avoid regressions. As usual any help in testing and finishing the remaining bits is appreciated. You can either comment in the above issue or join us on matrix.

As most of the logic lives in wireplumber and feedbackd very little of this is Phosh specific so this should readily be reusable by other graphical environments. Hopefully we can standardize on a common role based policy and standard set of stream tags. If anything is missing please comment in the xdg-spec issue.

Many thanks to the Wireplumber maintainers, especially George Kiagiadakis for the guidance, helpful code reviews and merging our changes and thanks to Arun Mani J for proof reading this post.

If you find this useful you can support our work on this and similar topics via donations.

Updates Link to heading

  • 2025-11-25: Update information on pipewire bug
  • 2025-12-30: Mention that 0.5.13 is released