[L’introduction est en français, le reste du texte en anglais]
PipeWire, implémentation et évaluation
Dans le premier article, nous avons préparé un système audio sur lequel nous pourrions remplacer le serveur de son. De plus, nous avons expliqué les principaux concepts de JACK et PulseAudio en matière d’enregistrement et de lecture audio. Cette deuxième partie présente PipeWire, comment l’utiliser dans notre système audio et mesure ses performances.
PipeWire
PipeWire is an open-source project for handling multimedia pipelines in Linux system. There is already a very good introduction about PipeWire which was made by Bootlin that everyone can refer to, which can be accessed here. The aim of this project is to provide a low-latency, graph-based processing engine on top of audio and video devices. Each element of the graph (sound device, application…) is called a node. The whole PipeWire system graph can be observed by running this command:
$pw-dot -a -d -9
This command will show every node in the Pipewire graph including the audio input/output nodes, client nodes and library nodes with details. Here is a view of our project’s entire graph.
In this project, we use WirePlumber as a modular session/policy manager. WirePlumber is an advanced session/policy manager, based on Glib/GObject. Its purpose is to wrap around PipeWire API, by using the daemon’s modules or the external tools to manage PipeWire functionality.
Source: https://www.collabora.com/news-and-blog/blog/2020/05/07/wireplumber-the-pipewire-session-manager/
During initialization, WirePlumber creates the nodes for Alsa playback and capture devices. It will also create the nodes for its clients, which are applications using PipeWire as server. A node consists of multiple ports, classified by their direction: input or output. PipeWire will use the links to connect these ports between different nodes together. A node created from Alsa devices with only the input ports is called an « Audio/Sink » and those with only the output ports is called an « Audio/Source ».
You can see here how PipeWire creates and connects the ports between CamillaDSP ports (cpal_client_*) and our Alsa device ports (*_sound_cs42448) together. The linking is done by a port mapping in API, but these links are interactive and can be manipulated by user. This means we can delete, create, or redirect a link to another port on the fly.
PipeWire can be used to replace both PulseAudio and JACK, by providing a PulseAudio-compatible server implementation and an ABI-compatible libraries for JACK clients.
JACK and PulseAudio replacement
To replace JACK server by PipeWire for running a JACK client, we must:
- Run PipeWire and Wireplumber (by program or by service).
- Run pw-jack right before the JACK Client in one command line. For example in our case:
$pw-jack camilladsp /etc/camilladsp/jackconfig.conf
pw-jack will force the JACK client to use PipeWire’s reimplementation of JACK libraries by changing the LD_LIBRARY_PATH environment variable, instead of the standard JACK libraries .
To replace PulseAudio server by PipeWire for running a PulseAudio client, we must:
- Run PipeWire, Wireplumber and PipeWire-pulse. PipeWire-pulse is a PulseAudio-compatible daemon that integrates with the PipeWire media server, which can be used to replace the standard PulseAudio.
- Check current daemon information by:
$pactl info
You should see this in the output: “Server Name: PulseAudio (on PipeWire x.x.x)”. This indicates that PipeWire daemon will be used by the PulseAudio clients instead of a standard Pulseaudio server.
- Run the PulseAudio client.
From the point of view of applications, they are still using JACK and PulseAudio API, but these APIs are in fact provided by PipeWire. This makes it possible for us to run at the same time a JACK client and a PulseAudio client, which required specific configuration before.
Example of running at a same time a JACK client, a PulseAudio client and a PipeWire client
Schema illustrating the way PulseAudio and JACK applications are supported
Source: https://bootlin.com/blog/an-introduction-to-PipeWire/
Performance Results
Wim Taymans, the developer of PipeWire, has already done a complete article about PipeWire performance, you can see it here. However, these tests are run on the desktop platforms and the results might be different for embedded systems.
In this article, we compare PipeWire with JACK, PulseAudio, and direct ALSA access through alsa-libs in the context of an embedded platform (i.MX8 Nano Ultralite). CPU usage and latency are the main comparison factors.
CamillaDSP with only Alsa-lib
We first test the performance when CamillaDSP is running only with Alsa, which means no sound server is used. In this case, CamillaDSP directly uses the Alsa-lib API to exchange audio data with the device through ALSA.
With htop, we can see that the process “CamillaDSP” consumes 28% of the CPU with three small threads.
The oscilloscope shows a latency around 4 ms:
As you can see, these results show a very good performance. The limitation is that only CamillaDSP can read/write samples to the audio device. If there is only one application, this would probably be the most efficient solution, otherwise this may be a limitation for complex use-cases involving multiple applications.
CamillaDSP with JACK and PipeWire-JACK
We compared JACK and PipeWire with 256 samples buffers for both sound servers. The CPU load measurements with CamillaDSP running, measured with htop, are shown below:
| CamillaDSP with JACK server | CamillaDSP with PipeWire server |
CamillaDSP | JACK | CamillaDSP | Pipewire |
%CPU | 28% | 18% | 16% | 14% |
Threads | 5 | 1 | 5 | 1 |
As can be seen, CamillaDSP running with Pipewire server consumes much less CPU, almost half of what it consumes when running with JACK server !
Then, we changed the value of buffer size in two cases in a range from 32 samples to 1024 to test the latency. For each buffer size, the full audio chain latency is measured with the oscilloscope. The results are displayed below:
Our results are similar to the results of Wim Taymans in his article. For small fixed buffer size, JACK seems to do better than PipeWire. But starting from buffer of 128 samples, PipeWire gradually becomes better in terms of system latency.
CamillaDSP with PulseAudio and PipeWire-Pulse
PulseAudio uses a dynamic buffer size to make the system as balanced as possible. In our case, the latency we measured is about 60 ms.
PipeWire-Pulse also uses a dynamic buffer latency, but we can force Pipewire to use a fixed value or to specify the maximum buffer size in its configuration. This means PipeWire-Pulse can have a better latency compare to PulseAudio if we ignore all other aspects, concerning the CPU usage and the configuration complexity. But to fairly compare CPU load, we set the latency of PipeWire-Pulse client to 60ms.
| CamillaDSP with PulseAudio server | CamillaDSP with PipeWire server |
CamillaDSP | PulseAudio | CamillaDSP | PipeWire | PipeWire-Pulse |
%CPU | 25% | 34% | 34% | 17% | 20% |
Threads | 5 | 1 | 5 | 1 | 1 |
The above results show that, when compared to standard PulseAudio at the same latency, PipeWire used more CPU due to having to run PipeWire daemon and PipeWire-Pulse daemon at the same time. This causes a high CPU usage in general although the latency is not too outstanding.
CPU usage of four previous cases when fixing the same latency
To make a CPU load comparison between all sound servers, we manually set their latency to 60ms. The following table details the CPU load comparison at this set latency, to show which solution is the most efficient.
| CamillaDSP with JACK | CamillaDSP with PipeWire-jack |
Process | CamillaDSP | JACK | CamillaDSP | PipeWire |
%CPU | 26.1 | 14.1 | 14.3 | 10.5 |
| CamillaDSP with PulseAudio | CamillaDSP with PipeWire & PipeWire-Pulse |
Process | CamillaDSP | PulseAudio | CamillaDSP | PipeWire | PipeWire-Pulse |
%CPU | 26.7 | 35.1 | 35.6 | 17.4 | 24.3 |
These results show that, while PipeWire itself is an efficient solution, the use of PipeWire-pulse hinders its performance. This makes it a good candidate for real-time designs as a JACK replacement, where it can be used to gain CPU load for other processes. However, as of now, further developments or configuration optimizations are required to make it a Pulseaudio replacement in use-cases where JACK compatibility is not needed.
Conclusion
PipeWire is still a young project compared to JACK and PulseAudio but it has really met the majority of users expectations in the embedded projects about its major strides, its convenience of new features and its performance. With the reimplementation of JACK and PulseAudio APIs, PipeWire does not require users to do any change to their application. Instead, users can still take advantage of its strengths, even combine them together.
In addition, the developer’s ambition for PipeWire is to be a multimedia platform, not only an audio server, which can also control image streams, so the future of PipeWire is to be able to manipulate the whole multimedia system in a project. This will be extremely convenient for developers who only have to master and to maintain one back-end technology and rely on established clients.