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:
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.
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"
media-role-nodes.conf into ~/.config/wireplumber/wireplumber.conf.d/ and restart
wireplumber via `systemctl –user restart.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