Published on 2021-03-31 by Kenneth Flak
Back to Tech Research
May you live in interesting times. Or, alternatively: Good luck getting professional audio properly configured on Linux. At least, this used to be the predicament. Getting ALSA to work together with Jack to work together with PulseAudio is an esoteric business worthy of the deepest forms of black magic. Now, however, Wim Taymans at Red Hat is working on something that might just bring us from the dark ages into an era of enlightenment, at least as far as the end user is concerned: pipewire.
The pipewire
promise is one of easy, plug-and-play configuration of
all your audio devices. No more worrying about whether your application
supports jack, PulseAudio or both. Now you should be able to just run
your application, and sound will magically appear from your speakers.
Just like that. Last week I decided it was time to check out this
fabulous new invention, and so I set about making it work for me. Here's
a preliminary tour of what I found out:
I wanted the full integration, so I ran:
paru -S pipewire pipewire-{alsa,jack,wireplumber,pulse}
EDIT 2023-07-03: media-session
is now deprecated. Use wireplumber
instead.
All the packages except pipewire-jack-dropin
are in the extra
repo.
pipewire-jack-dropin
is in AUR. However, if you prefer, you could
simply remove your existing jack/jack2 packages instead of installing
the dropin. Replace paru
with your preferred AUR helper.
If you, like me, run jack on login, then make sure to disable whatever script you're currently running. Reboot and you should be good to go.
In short: this is very, very promising. pipewire
emulates jack
whenever a jack
client is launched. No need to manually start a
server. Also: For the first time since I abandoned macOS for Linux my
computer automatically swapped soundcard when I plugged in my Fireface
UCX. No fiddling around with custom-made scripts to switch between
internal and external interfaces.
I might have gotten a bit more xruns than I would normally gotten, but not show-stoppingly so.
EDIT 2023-07-03: xruns are no longer an issue for me when running with a vanilla kernel.
There is very little need for any specific configuration, which
was almost shocking for a long-time Linux musician. Currently
the samplerate of pipewire is fixed globally, and can be set in
/etc/pipewire/pipewire.conf
. Uncomment this line:
default.clock.rate = 48000
and set it to whatever you want it to be, provided your hardware supports it.
Slightly confusingly, you can also set this for jack in
/etc/pipewire/jack.conf
, in the jack.properties
section.
jack.properties = {
node.latency = 256/48000
...
}
This will give you a default buffer size of 256 samples at a samplerate of 48khz. If your audio interface is not stellar, you'd probably want to go for 512 or even 1024 samples to avoid xruns at the expense of higher latency. If your interface is excellent you might go lower, into the warp speed area of 64 or even 32 samples.
The buffer size can also be set in the client itself. In ardour
I
was able to set this in the settings. Alternatively you can set an
environment variable before running your application:
PIPEWIRE_LATENCY=256/48000 your_application
Notice that this will only change the ratio between buffer size and
samplerate. It is not (yet) possible to change samplerate at runtime, so
if you try something like this while the jack.properties node.latency
is set to 48000:
PIPEWIRE_LATENCY=256/96000 your_application
... you will end up with a buffer size of 128 and a samplerate of 48k. Ratios, ratios, ratios.
Pipewire will resample audio as required to get PulseAudio, jack and ALSA working together.
So, in short: set the samplerate globally, mess around with buffer sizes per application. More details on configuration can be found [here](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Configur ation)
EDIT 2023-07-03: It is possible to set the global samplerate during runtime by issuing the command:
pw-metadata -n settings 0 clock.rate <value>
The default configuration is of the consumer kind, which means stereo
in and stereo out. If you invested big money in 18 channels I/O this
seems a bit sad. The remedy is to activate the pro
profile. I
haven't figured out how to do this on the command line yet, but it's
very easy to do this in PulseAudio Volume Control (PAVU). Go to the
Configuration
tab, click on the Profile
drop-down menu under your
card, select Pro Audio
and you should be good to go. All the channels
should now be available.
For some reason all my jack applications insisted on playing back on the
internal soundcard by default. The solution was, yet again, found in the
PAVU application. Go to the Output Devices
tab, find the interface you
want to use as default, click on the green checkbox on the right side
to make this your default output. Repeat the same steps in the Input Devices
.
The Fireface UCX is a brilliant soundcard in many ways, but on Linux you
will only get the class-compliant experience, which means zero control
over levels of individual input and output channels on the software
side. To set the levels you have to use the rotary encoder in the front
of the soundcard, an encoder that has died on me more than once. A very
unfortunate weakness, in other words. Now, however, this problem can be
mitigated through PAVU. Next to the previously mentioned green checkbox
in the Output
and Input Devices
there is a padlock button. Click
on this, and voila! All your input and output stream levels become
individually accessible! Of course, it is possible to pull off this kind
of trick in any number of jack mixing applications, but the option to do
this directly in a central configuration application makes life just a
bit easier.
I haven't yet found an easy way to check the xrun count, but pipewire helpfully posts any information about this to the systemd logs. So, to keep an eye on the state of your pipewire instance in real time, you could simple run this in a separate terminal:
journalctl -f | grep pipewire
This will give you a running commentary on the state of your audio stack. An xrun will look something like this:
Apr 01 10:44:04 t480s pipewire[10997]: (alsa_output.usb-RME_Fireface_UCX__23815246__F9C767BDDD2EEC8-00.pro-output-0-49) client too slow! rate:256/48000 pos:294748928 status:triggered
Apr 01 10:44:04 t480s pipewire[10997]: (alsa_output.usb-RME_Fireface_UCX__23815246__F9C767BDDD2EEC8-00.pro-output-0-49) XRun! rate:256/48000 count:19 time:23386594654 delay:22444 max:22444
EDIT on 4 April 2021: after publishing this, I was made aware of an
extremely handy tool that comes bundled with pipewire
: pw-top
. This
lets you monitor all your sinks and sources; their samplerates and
buffer sizes, xrun counts, process ids and more. Very useful! Next on my
todo-list is to write an xrun-counter for my sway/i3 status bar.
EDIT 2023-07-03: I am currently using waybar, which has a very handy
jack
module. This one flashes red on any xruns. My config looks like
this:
"jack": {
"format": " DSP {load}% / {latency} ms / {bufsize} ",
"format-xrun": "{xruns} xruns",
"format-disconnected": "DSP off",
"realtime": true,
"on-click": "qjackctl"
},
Zoom seems to work without issues or further configuration. I had some trouble getting Jitsi up and running in Firefox. In the Chromium-base Qutebrowser I could make it work after a restart and a segfault in pipewire.
Added sectoin 2024-01-01: wireplumber
gives you the possibility to
customize your configuration from here to eternity. This comes in very
handy when you want to set different latencies for different soundcards.
If, for example, you would like your shitty, internal card to have a
nice, big hardware buffer with a correspondingly big latency, and your
shiny, beautiful external soundcard to have a smaller hardware buffer,
you can set that up in ~/.config/wireplumber/main.lua.d
. This is
a directory with lua scripts that are run by wireplumber, and that
overrides the defaults you set elsewhere. The files should be named
50-or-larger-somethingsomething.lua
. You could use 51-internal.lua
to set a higher alsa buffer size than the default, and change the nick
that shows up when querying the status of the soundcard:
rule = {
matches = {
{
{"node.name", "equals", "alsa_card.pci-0000_00_1f.2"}
}
},
apply_properties = {
-- ["device.disabled"] = true
-- ["api.alsa.period-size"] = 2,
["api.alsa.period-num"] = 4096,
["node.nick"] = "ALC257",
}
}
table.insert(alsa_monitor.rules, rule)
It is still early days of development, and hickups are to be expected. However, my overall feeling is that this is a massive improvement on the user experience over what used to be the labyrinthine tangle that professional audio Linux users have had to suffer over the years. I am looking forward to pipewire-native ways of setting samplerates, buffer sizes and so on at runtime, preferably using the command line.
EDIT 2023-07-03: I am now fully back on Pipewire after having had some troubles with xruns, and am very, very happy with how it works. I also no longer need to run a realtime kernel, a vanilla kernel is more than sufficient to get lower latencies than I have ever experienced before, as the regular kernel is running PREEMPT by default.
EDIT 2024-01-01: Now that Pipewire has reached the 1.0 milestone, there's really no reason not to use it any more...