Table of Contents:
PipeWire is de facto the new audio server used by all major Linux distributions (including Debian and Apertis). It provides a low-latency, graph-based processing engine on top of audio and video devices that can be used to support the use cases currently handled by both PulseAudio and JACK.
The goal for this task is to evaluate PipeWire in known problematic use-cases with JACK, primarily in supporting multiple audio streams and dynamic switching between them between outputs.
This evaluation is based on Apertis v2023 and PipeWire 0.3.59.
Set-up the R-Car board
To evaluate Pipewire, we will use an R-Car Starter Kit Premier board
(RTP8J779M1ASKB0SK0SA003
) with several Leagy USB Sound Cards
(also available at www.conrad.de).
The first step is to install Apertis v2023 (previous Apertis releases don’t support R-Car audio output) on the board eMMC. Running tests from Apertis installed in an SD card is not recommended as it may reduce performance.
The support of R-Car audio output has recently been added to the Apertis v2023 linux kernel, thus we need at least linux 6.1.20-2~bpo11+1+apertis4.
To avoid having to update the kernel after installing Apertis, we will flash a daily image (i.e. built after 2023.10.10) of Apertis which includes the required kernel.
The easiest way to achieve that is first to flash Apertis on an SD card, then use this SD card to boot the board. From there, we can flash Apertis on the eMMC.
How to flash Apertis on SD card
First, insert your SD card in a computer then run fdisk -l
to identify by
which device it is referenced:
$ sudo fdisk -l
Disk /dev/mmcblk0: 29.72 GiB, 31914983424 bytes, 62333952 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Device Start End Sectors Size Type
/dev/mmcblk0p1 34 500000 499967 244.1M EFI System
/dev/mmcblk0p2 500001 5859375 5359375 2.6G Linux filesystem
/dev/mmcblk0p3 5859376 7812466 1953091 953.7M Linux filesystem
In our example, SD card is /dev/mmcblk0
. Now, we can tell bmaptool to copy our
Apertis image to the SD card:
$ sudo bmaptool copy https://images.apertis.org/daily/v2023/20231010.0115/arm64/fixedfunction/apertis_v2023-fixedfunction-arm64-uboot_20231010.0115.img.gz /dev/mmcblk0
For this evaluation, we flashed a daily FixedFunction (APT-based) image of Apertis.
Once copying is complete, the SD card is ready to go in our R-car board.
How to flash Apertis on eMMC
Before, booting your board, you will have to use a microUSB cable to connect the PC to R-Car board and then open a serial console with for instance picocom:
$ sudo dmesg | tail # To help identifying the tty device name
$ sudo picocom -b 115200 /dev/ttyUSB0
Now, it’s time to boot the board, if u-boot is correctly configured, apertis will start otherwise, you will have to re-install a new one (please refer to the Apertis documentation to update u-boot, see rcar-gen3_setup).
Once, you have access to the Apertis shell, the first step is to enable the
development
repository in order to install bmap-tools
. This can be done by
adding:
deb https://repositories.apertis.org/apertis/ v2023 target development
to /etc/apt/sources.list
.
$ sudo vi /etc/apt/sources.list
$ sudo apt update && sudo apt install bmap-tools
Once, bmap-tools
is installed, you can flash Apertis on the eMMC. Please refer
to steps above to identify the device name of your eMMC and how to use bmaptool copy
.
When, Apertis is flashed on the eMMC, it’s time to remove the SD card from the board and to reboot the board.
How to check if sound works
After booting Apertis from the eMMC, we need to enable again the development
repository on this new system because we will need tools only available in
development
for our tests (i.e. alsa-utils
, sndfile-tools
, graphviz
and
python3-matplotlib
). At the time of writing this report, the updates
repository was also required for sndfile-tools
, but this is no longer relevant
since the folding happened. sndfile-tools
is now available in the main repository.
Now, we can install some tools that will be used in the next steps:
$ sudo apt update
$ sudo apt install alsa-utils pulseaudio-utils \
pipewire-jack sndfile-tools \
psmisc graphviz python3 python3-matplotlib \
stress-ng
- alsa-utils will be used to test sound cards directly using ALSA.
- pulseaudio-utils gives us
pactl
to check if pipewire is running. - pipewire-jack provides the PipeWire implementation of JACK, it’s the one
we are going to evaluate using
pw-jack
. - sndfile-tools for its
sndfile-jackplay
allowing us to play wav files with JACK. - psmisc will install
killall
which is used during tests to kill some processes. - graphviz provides
dot
which can convert dot files generated bypw-dot
into png files to represent the PipeWire graph. - python3 to execute python test scripts.
- python3-matplotlib to generate graph from results.
- stress-ng to simulate a high system load.
If the linux kernel is recent enough, you should have sound cards registrered:
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: rcarsound [rcar-sound], device 0: rsnd-dai.0-ak4613-hifi ak4613-hifi-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: rcarsound [rcar-sound], device 1: rsnd-dai.1-i2s-hifi i2s-hifi-1 []
Subdevices: 1/1
Subdevice #0: subdevice #0
The tool speaker-test
can be used to verify if speakers can make sound, but
before we need to bump the volume of DVC Out
with:
alsamixer -D hw:0
Select the item DVC Out
with the left and right arrows and then use the up and
down arrows to adjust the volume. Now, speaker-test
should make noise (be
careful when using a headset because the volume can be quite loud).
Now that the sound works, it is time to connect all devices together.
Setup for pipewire evaluation
A USB sound card is connected to the board while the other two are connected to the laptop for capture. This is a picture of our setup:
Speakers (or earphones in our example) can be plugged in the LINE OUT of board’s sound cards in order to confirm it works as expected:
# Identify playback device
$ aplay -l
# Test onboard sound card output
speaker-test -Dplughw:CARD=rcarsound -c2
# Test USB sound card output
speaker-test -Dplughw:CARD=ICUSBAUDIO7D -c2
Before running tests, we need to substitute earphones with jack cables plugged on the other side in the LINE IN of the two other capturing sound cards:
Test case 1: Switching audio streams onto running devices
This test case is meant to validate that adding or switching an audio stream in a running output neither impacts that output or any other system outputs. To do this both available outputs will receive an audio stream (from a jack based application) with a unique constant tone. A third audio stream (with a seperate tone) will be used for the switching test and every 5 second switches from the onboard to the usb audio output.
Three scripts are available to run this test test-case-capture.py
,
test-case-1-run.py
and generate_wav.sh
.
- The first one is to capture sound coming from the sound cards of the board and
has to be run on the laptop side. This script uses
pw-record
to record sound that meanspipewire
must be used on the computer doing the capture (for example by using Apertis SDK or Debian Bookworm). From this script, two wav files are generated. - The second script starts two different streams on both board sound cards (onboard and USB), and then it will start a third steam and will switch the output every each 5 seconds.
- The last one generates wav files using GStreamer. These files will be played
during tests with
pw-jack sndfile-jackplay
.
Results
During the test, two wav files are generated test-case-capture1.wav
and
test-case-capture2.wav
(both are available in results/test-case-1/
). The
first one captures the sound coming from the board USB sound card while the
second capture the sound coming from the board built-in sound card.
At the beginning of test-case-capture1.wav
, we can hear a mixture of both
tone test300.wav
and test800.wav
while for test-case-capture2.wav
only the
tone from test300.wav
is audible.
At ~ 7 sec, the tone from test800.wav
is transferred from the USB card to the
built-it card. In other words, we stop hearing it in test-case-capture1.wav
and we start hearing it in the other file test-case-capture2.wav
. The switch
of the test800.wav
tone appears every 5 secs, the first one is at ~ 7 secs,
then 12, 17, etc until 37 secs then the capture stops.
When the switch occurs, we don’t hear any silence or any artefact like cracking. The sound coming from the different outputs remains smooth when a new stream is added or removed.
Test case 2: Audio input reconfiguration
As with the first test case both outputs will receive constant audio tones; On top of that the onboard microphone output will be capture via loopback and also output via the onboard line-out. The audio captured will be reconfigured from a rate of 8K to 16K and vice-versa every few second to validate that this doesn’t impact either the direct output or the unrelated output.
For this test case, we use the previous setup as basis then we plug a microphone (in our example, we use the microphone of the white earset) into the MIC INPUT of the board.
NOTE: The built-in sound card is affected by a hardward limitation preventing to independently change the rate of input and output. Since the aim of this test is to evaluate pipewire and not the built-in sound card, the audio input reconfiguration is done on the USB sound card and not on the built-in sound card. In other words, we capture sound with the USB card
LINE In
and redirect it to theLINE Out
of the same card (which differs from the diagram and picture above). The reason for this apparently limitation is that the onboard R-Car card only has one reference clock available for the input/outputs. Which means both input and output rates need to be compatible with the current clock rate, hence the requirement to reconfigure both whenever changing that clock rate. Other hardware (like the USB card in this case) often has seperate clocks for different inputs and outputs and hence is capable of switching rate independently.
The script test-case-2-run.py
does the same setup as test-case-1-run.py
,
it only differs by not starting a third moving stream but instead capture the
USB LINE In
and redirect it to the USB LINE Out
by changing every 10
secs the capture sample rate. Due to a hardward limitation, the capture sample
rate is reconfigured between 44100 and 48000 instead of the unspported 8000 and
16000.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. with changing capture sample rate of the
stream redirected to 1 output) to evaluate how the sound is affected.
Results
Currently, it’s not possible to reconfigure a running device. That means, to reconfigure the sample rate input, we have to force the device input to be idle.
During the test, two wav files are generated test-case-capture1.wav
and
test-case-capture2.wav
(both are available in results/test-case-2/
). The
first one captures the sound coming from the board built-in sound card while the
second capture the sound coming from the board USB sound card.
In test-case-capture1.wav
, we can hear the test300.wav
tone without any
artefact and without any interruption. This stream is therefore not affected by
the reconfiguration of the input of the other sound card.
In the other file test-case-capture2.wav
, the stream starts with the
test300.wav
tone, then at 0:10 we start hearing the test800.wav
tone which
is captured at 44100 Hz and mixed to the first tone. We stop hearing the
test800.wav
tone at 0:20 while the test300.wav
tone is still audible. This
pause of the test800.wav
tone continues for 5 secs allowing the reconfigure the
capture input rate at 48000 Hz by waiting the input device to be idle. Then, at
0:25 we hear again a mix of test300.wav
and test800.wav
captured at 48000 Hz
for 10 secs. At 0:35, we stop hearing the test800.wav
tone because of the
reconfiguration at 44100 Hz, which restart 5 secs later and so on until 1:10.
The sample rate reconfiguration is checked during the test in the FORMAT
column
of the pw-top
output.
In this case, only a part of the direct output is affected by the reconfiguration.
The stream which is not reconfigured (i.e. coming from pw-jack sndfile-jackplay
)
is not affected, whereas the captured stream which is reconfigured is affected.
Indeed, to apply the reconfiguration we have to wait the input device to be idle
to make pipewire apply the new configuration. The default timeout for switching
a device to idle is defined in a wireplumber config file
(i.e. /usr/share/wireplumber/main.lua.d/50-alsa-config.lua
) at 5 secondes
(see session.suspend-timeout-seconds). It should be possible to reduce the
timeout to only 1 seconde (setting 0
disables suspend on idle, so not the
desired behavior), but that will still impact the stream.
To summarize, the reconfiguration of audio capture only impacts the captured stream and not the direct output since we have to stop the stream to make pipewire apply the changes. Morevoer, the unrelated output is not affected. If feasible, adding a way to force pipewire to reconfigure a device without impacting (or at least by reducing its impact) the current stream is probably a way to go.
Test case 3: Capturing multiple inputs to multiple outputs
As in previous test case as a basis each output will get a unique tone again. Also audio from both inputs is captured via loopbacks and output via a unique specfic channel on each output, for example the onboard mic input will be output via the left channel of both outputs and the usb microphone via the right channel. Overall each output will get a mixed stream of its unique tone and and one microphone input in each of its channels. The goal here is to validate that starting, stopping inputs independently does not cause disruptions in the outputs.
This test case requires the setup used in the test case 2 to which we add a new source sound (here we use a music player) plugged into the LINE IN of the board USB sound card.
The script test-case-3-run.py
does the same setup as test-case-1-run.py
,
it only differs by not starting a third moving stream but instead captures one
input and redirects it to a unique channel of both outputs and capture the other
input to redirect it to a other channel of both outputs. Then, the capture is
stopped and (re)started repeatedly.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. repeatedly starting and stopping the
capture) to evaluate how the sound is affected.
Results
Both output were recorded in test-case-capture1.wav
and test-case-capture2.wav
,
respectively from the USB and built-in sound cards.
In test-case-capture1.wav
, we hear on both channels (left and right sides)
the test300.wav
tone played with pw-jack sndfile-jackplay
. Moreover, we can
hear on the left channel the sound captured from the MIC Input
of the board
whereas on the right channel we can hear the test800.wav
tone coming from the
music player through the LINE IN
input of the USB sound card. The capture of
both inputs is stopped and restarted every 5 secondes, in other words we have 5
secondes of capture then 5 secondes without capture and so on.
In the other recorded file test-case-capture2.wav
, we hear on both channels
the test800.wav
tone played with pw-jack sndfile-jackplay
. And like for this
previous file, the left side captured the MIC Input
of the board whereas the
left side captured the LINE IN
of the USB sound card i.e. the test800.wav
tone.
In both recordings, we cannot detect any disruptions in the outputs when starting
and/or stopping the differents inputs. But, on these two recordings, we have noise
during the capture. Both sides give different artefacts, the left (capturing the
MIC Input
) is mainly affected by hatching sound whereas the right (capturing
the LINE IN
) is mainly affected by cracking noise.
In order to identify the cause of these artefacts, the same test was done without
playing tones with pw-jack sndfile-jackplay
. The result was similar with same
artefacts (results are available in results/test-case-3/without-jackplay
).
Redirecting only one stream on one side (so without mixing several streams) gives
a good quality result without any artefact.
Adding an additional intermidiate node to couple streams before routing them to
outputs doesn’t give better results. See test-case-3-add-intermediate-nodes.sh
for the manual steps.
We are evaluating the pipewire version provided by apertis v2023 i.e. pipewire
0.3.59, however this use-case might have improved in more recent versions. A quick
look into the changelog after 0.3.59 shows us improvements in pipewire-jack
,
audioresampler
, etc that could fix issues we are facing.
Test case 4: Benchmarking load caused by a bigger number of clients
In this test case only the onboard audio output will be used. Up to 50 audio clients generating a constant tone will be running in parallel; Next to that a test application will connected to pipewire, output a short tone and exit; Measuring how long it takes to connect and play back the tone.
The goal here is to benchmark if more audio clients have an impact on initial time to playing back the audio.
The script test-case-4-run.py
measures how long it takes to run a
pw-jack sndfile-jackplay my_sound.wav
on the board after having started an
increasing instance number of other pw-jack sndfile-jackplay
. In other words,
it will measure the time required to run pw-jack sndfile-jackplay
when only 1
other instance is running, then with 11 other instances, 21 other instances, etc
until 51 instances.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. with many instances of
pw-jack sndfile-jackplay
) to evaluate how the sound is affected.
Results
To eliminate random variability, each scenario is performed ten times then a mean is computed to provide the most accurate overview of pipewire capabilities.
In the graph below, the horizontal axis is the number of already running
sndfile-jackplay
instances (from 1 to 51) whereas the vertical axis is the time
required to start a new pw-jack sndfile-jackplay
and to play the file
/usr/share/sounds/alsa/Front_Center.wav
(lasting 1.428 secs).
The red line is drawn from the means and every blue dot is a measure of required
time.
As we can see, the time starts to increase when we already have 30 stream running. Indeed, the means is 1.499 secs for both for 1 and 11 running streams and 1.503 secs for 21 streams (+ 4ms). From here, the time will increase to 1.511 secs for 31 streams (+ 12ms) and it will reach 1.524 secs for 41 streams and 51 streams (+ 25ms).
To summarize, we can see a variability in the order of ~ 30 ms for each set of tests, for example with 1 instance the time ranges from 1.49 to 1.52 secs, and the same for 51 instances from 1.51 to 1.54 secs. By comparing the maximum of each set, 1.52 secs for 1 instance whereas it is 1.54 secs for 51 instances, we see a variability of in the order of ~ 20 ms. The same variability of ~ 20 ms is seen when comparing the minimum, 1.49 sec for 1 instance vs 1.51 for 51 instances.
This variability is likely only noise and the actual longer times could simply be due to higher system load as opposed to pipewire API. While there is an overall upward trend in the measurements, it’s both quite small. With only a 20 ms increase in end to end latency on average and 50 ms or so between absolute min and max, we can conclude there is no big impact with regards to the number of clients. For measuring more detailed impacts another approach would be needed.
All raw results are available in results/test-case-4/
, including a list of
means test-case-4-list-means.txt
, a list of every measure
test-case-4-list-results.txt
and a capture of the output test-case-capture1.wav
.
Test case 5: xrun behaviour under load
In this test the same setup as Test case 1 will be used. To test the xrun behavior the CPU availability for pipewire will be constrained such that it actually triggers xruns. The goal here is to evaluate how pipewire responds to that situation and whether/how it recovers properly when CPU becomes available again.
The script test-case-5-run.py
does the same setup as test-case-1-run.py
,
it only differs by not starting a third moving stream but instead by using
stress-ng
to simulate a high CPU load in order to generate xruns. The number
of xruns will be measured with pw-top
and a profil is generated by pw-profiler
.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. under high system load) to evaluate how
the sound is affected.
Results
stress-ng
has completed its run in 92.91s (1 min, 32.91 secs) (for a requested
timeout of 1 min). This run was performed with --class cpu --all 20
, that means
20 instances of all tests from the cpu
class were running simultaneously. The
aim was to trigger xruns to check how pipewire responds and how it recovers
properly after the load. Before starting to stress the CPU, pw-top
reported
0 xrun all for objects which is expected, while after the stressful period,
548 xruns are reported for the node associated to the built-in sound card and
0 xrun for the node associated to the USB sound card.
In the captured streams, test-case-capture1.wav
from the USB sound card
and test-case-capture2.wav
from the built-in sound card (both available in
results/test-case-5/
), we begin to hear the effects of the load at 0:34
until 1:40.
For the sound coming from the USB sound card (so without any xrun counted), we hear crackling sound during 1min00 which corresponds to the duration of the stress run. The effect is much more pronounced on the sound coming from the built-in sound card as there was mostly silence with spaced crackling for 1min06 during this time. Interestingly, the USB sound card is less affected than the built-in sound card.
Because the sound disturbances are expected and desired in this test since, we intentionally generated an intense CPU usage. The main point here is how pipewire recovers after the stress. From the USB sound card point of view, pipewire recovers almost immediately as the disturbances last for 1min00. Regarding the built-in sound card, it’s a little longer with 1min06 (+6secs) to fully recover.
Results summary
- Adding or removing an audio stream in a running output neither impacts that output or any other system outputs. See Test case 1: Results for more details.
- Reconfigurating of audio capture only impacts the reconfigured stream, as we have to make the input device idle to force pipewire to apply the changes. The direct or unrelated outputs are not affected themselves by the reconfiguration. See Test case 2: Results for more details.
- Starting and/or stopping inputs independently does not cause disruptions in the outputs, but mixing streams from different sources gives sound arfects during our test. See Test case 3: Results for more details.
- The results show a variability between the different sets of tests which is quite small and which is probably only noise and resulting from the difference of system load levels as opposed to pipewire API. There is no big impact with regards to the number of clients. For measuring more detailed impacts another approach would be needed. See Test case 4: Results for more details.
- Interestingly, both sound cards (USB and built-in) are not equally affected by a CPU limitation. Pipewire manages to keep sound (although of poor quality because chopped) on the USB sound card but not for the built-in sound card which mostly emits only silence. Pipewire recovers almost instantly in the first case, and with a delay of a few seconds in the other case. See Test case 5: Results for more details.
Evaluation of PipeWire with Apertis v2024
A first evaluation of PipeWire 0.3.59 on R-Car was done with Apertis v2023. Because the results present some artefacts, the same evaluation has been repeated with Apertis v2024pre and PipeWire 0.3.84.
Please refer to Set-up the R-Car board for the set-up of the R-Car board. Here, the only difference is the Apertis image flashed on the board, we use the daily (20231110.0016) FixedFunction (APT-based) image of Apertis v2024pre.
$ sudo bmaptool copy https://images.apertis.org/daily/v2024pre/20231110.0016/arm64/fixedfunction/apertis_v2024pre-fixedfunction-arm64-uboot_20231110.0016.img.gz /dev/mmcblk0
As reminder, this is the setup used: A USB sound card is connected to the board while the other two are connected to the laptop for capture. This is a picture of our setup:
Speakers (or earphones in our example) can be plugged in the LINE OUT of board’s sound cards in order to confirm it works as expected:
# Identify playback device
$ aplay -l
# Test onboard sound card output
speaker-test -Dplughw:CARD=rcarsound -c2
# Test USB sound card output
speaker-test -Dplughw:CARD=ICUSBAUDIO7D -c2
Before running tests, we need to substitute earphones with jack cables plugged on the other side in the LINE IN of the two other capturing sound cards:
Test case 1: Switching audio streams onto running devices
This test case is meant to validate that adding or switching an audio stream in a running output neither impacts that output or any other system outputs. To do this both available outputs will receive an audio stream (from a jack based application) with a unique constant tone. A third audio stream (with a seperate tone) will be used for the switching test and every 5 second switches from the onboard to the usb audio output.
Results
During the test, two wav files are generated test-case-capture1.wav
and
test-case-capture2.wav
(both are available in results_v2024/test-case-1/
).
The first one captures the sound coming from the board USB sound card while the
second capture the sound coming from the board built-in sound card.
At the beginning of test-case-capture1.wav
, we can hear the tone test300.wav
while for test-case-capture2.wav
we hear a mixture of both tone test300.wav
and test800.wav
. At 0:10, the switch happens, and the test800.wav
tone is
transferred to test-case-capture1.wav
. A switch of test800.wav
happens every
5 secs until the end of the captured stream.
Like for the previous evaluation in Apertis v2023, when a switch happens, we don’t hear any silence or any artefact like cracking. The sound coming from the different outputs remains smooth when a new stream is added or removed. There is no regression and/or improvement compared to the previous evaluation.
Test case 2: Audio input reconfiguration
As with the first test case both outputs will receive constant audio tones; On top of that the onboard microphone output will be capture via loopback and also output via the onboard line-out. The audio captured will be reconfigured from a rate of 8K to 16K and vice-versa every few second to validate that this doesn’t impact either the direct output or the unrelated output.
For this test case, we use the previous setup as basis then we plug a microphone (in our example, we use the microphone of the white earset) into the MIC INPUT of the board.
NOTE: The built-in sound card is affected by a hardward limitation preventing to independently change the rate of input and output. Since the aim of this test is to evaluate pipewire and not the built-in sound card, the audio input reconfiguration is done on the USB sound card and not on the built-in sound card. In other words, we capture sound with the USB card
LINE In
and redirect it to theLINE Out
of the same card (which differs from the diagram and picture above). The reason for this apparently limitation is that the onboard R-Car card only has one reference clock available for the input/outputs. Which means both input and output rates need to be compatible with the current clock rate, hence the requirement to reconfigure both whenever changing that clock rate. Other hardware (like the USB card in this case) often has seperate clocks for different inputs and outputs and hence is capable of switching rate independently.
The script test-case-2-run.py
does the same setup as test-case-1-run.py
,
it only differs by not starting a third moving stream but instead capture the
USB LINE In
and redirect it to the USB LINE Out
by changing every 10
secs the capture sample rate. Due to a hardward limitation, the capture sample
rate is reconfigured between 44100 and 48000 instead of the unspported 8000 and
16000.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. with changing capture sample rate of the
stream redirected to 1 output) to evaluate how the sound is affected.
Results
Like for the previous evaluation , it’s not possible to reconfigure a running device. That means, to reconfigure the sample rate input, we have to force the device input to be idle.
The script for this test was adjusted for the new pipewire behavior. In the previous evaluation, pipewire assigned each JACK client to a unique node group based on their PID (i.e. “jack-PID”). With recent versions of pipewire, all JACK clients are assigned to the same node group called “group.dsp.0” (see commit bafa890a). The aim of this change was to schedule all JACK clients together, but this change affects our test since we want to reconfigure only one node without touching the other ones. However, it is possible to force our different JACK clients to be in different groups allowing us to restore the previous pipewire behavior and to reconfigure only specific JACK nodes. The node group is defined within the PIPEWIRE_PROPS variable when the client is started:
# Start a first instance of sndfile-jackplay which is assigned to the "group_A" node group
PIPEWIRE_PROPS='{ node.group = group_A }' pw-jack sndfile-jackplay test300.wav
# Start a second instance of sndfile-jackplay which is assigned to the "group_B" node group
PIPEWIRE_PROPS='{ node.group = group_B }' pw-jack sndfile-jackplay test300.wav
During the test, two wav files are generated test-case-capture1.wav
and
test-case-capture2.wav
(both are available in results_v2024/test-case-2/
).
The first one captures the sound coming from the board built-in sound card while
the second capture the sound coming from the board USB sound card.
In test-case-capture1.wav
, we can hear the test300.wav
tone without any
artefact and without any interruption. This stream is therefore not affected by
the reconfiguration of the input of the other sound card.
In the file test-case-capture2.wav
, the stream starts with the test300.wav
tone, then at 0:10 we start hearing the test800.wav
tone which is captured
at 44100 Hz and mixed to the first tone. We stop hearing the test800.wav
tone
at 0:20 while the test300.wav
tone is still audible. This pause of the
test800.wav
tone continues for 5 secs allowing the reconfigure the capture
input rate at 48000 Hz by waiting the input device to be idle. Then, at 0:25
we hear again a mix of test300.wav
and test800.wav
captured at 48000 Hz
for 10 secs. At 0:35, we stop hearing the test800.wav
tone because of the
reconfiguration at 44100 Hz, which restart 5 secs later and so.
The sample rate reconfiguration is checked during the test in the FORMAT
column
of the pw-top
output.
There is no change compared to the previous evaluation. The reconfiguration of audio capture only impacts the captured stream and not the direct output since we have to stop the stream to make pipewire apply the changes. Morevoer, the unrelated output is not affected.
Test case 3: Capturing multiple inputs to multiple outputs
As in previous test case as a basis each output will get a unique tone again. Also audio from both inputs is captured via loopbacks and output via a unique specfic channel on each output, for example the onboard mic input will be output via the left channel of both outputs and the usb microphone via the right channel. Overall each output will get a mixed stream of its unique tone and and one microphone input in each of its channels. The goal here is to validate that starting, stopping inputs independently does not cause disruptions in the outputs.
This test case requires the setup used in the test case 2 to which we add a new source sound (here we use a music player) plugged into the LINE IN of the board USB sound card.
The script test-case-3-run.py
does the same setup as test-case-1-run.py
,
it only differs by not starting a third moving stream but instead captures one
input and redirects it to a unique channel of both outputs and capture the other
input to redirect it to a other channel of both outputs. Then, the capture is
stopped and (re)started repeatedly.
The script test-case-capture.py
can be used to record sound generated by the
board sound cards during the test (i.e. repeatedly starting and stopping the
capture) to evaluate how the sound is affected.
Results
Both output were recorded in test-case-capture1.wav
and test-case-capture2.wav
,
respectively from the USB and built-in sound cards.
In test-case-capture1.wav
, we hear on both channels (left and right sides)
the test300.wav
tone played with pw-jack sndfile-jackplay
. Moreover, we can
hear on the left channel the sound captured from the MIC Input
of the board
whereas on the right channel we can hear the test800.wav
tone coming from the
music player through the LINE IN
input of the USB sound card. The capture of
both inputs is stopped and restarted every 5 secondes, in other words we have 5
secondes of capture then 5 secondes without capture and so on.
In the other recorded file test-case-capture2.wav
, we hear on both channels
the test800.wav
tone played with pw-jack sndfile-jackplay
. And like for this
previous file, the left side captured the MIC Input
of the board whereas the
left side captured the LINE IN
of the USB sound card i.e. the test800.wav
tone.
In both recordings, we cannot detect any disruptions in the outputs when starting and/or stopping the differents inputs. In both recordings, we don’t hear sound artefacts like in the previous evaluation. Streams are smooth without hatching sound or loud cracking noise. We can hear few light cracking, but this probably only due to the microphone saturation because the microphone is not the best quality.
Test case 4: Benchmarking load caused by a bigger number of clients
In this test case only the onboard audio output will be used. Up to 50 audio clients generating a constant tone will be running in parallel; Next to that a test application will connected to pipewire, output a short tone and exit; Measuring how long it takes to connect and play back the tone.
The goal here is to benchmark if more audio clients have an impact on initial time to playing back the audio.
Results
To eliminate random variability, each scenario is performed ten times then a mean is computed to provide the most accurate overview of pipewire capabilities.
In the graph below, the horizontal axis is the number of already running
sndfile-jackplay
instances (from 1 to 51) whereas the vertical axis is the time
required to start a new pw-jack sndfile-jackplay
and to play the file
/usr/share/sounds/alsa/Front_Center.wav
(lasting 1.428 secs).
The red line is drawn from the means and every blue dot is a measure of required
time.
As we can see, the time starts to increase when we already have 30 stream running. Indeed, the means is ~ 1.48 secs for 1, 11 and 21 running streams. From here, the time will increase to 1.492 secs for 31 streams (+ 12ms), to 1.507 secs for 41 streams (+ 27ms) and it will reach 1.511 secs for 51 streams (+ 31ms).
To summarize, we can see a variability in the order of ~ 30 ms for each set of tests, for example with 1 instance the time ranges from 1.46 to 1.49 secs, and the same for 51 instances from 1.50 to 1.53 secs. By comparing the maximum of each set, 1.49 secs for 1 instance whereas it is 1.53 secs for 51 instances, we see a variability of in the order of ~ 40 ms. The same variability of ~ 20 ms is seen when comparing the minimum, 1.46 sec for 1 instance vs 1.50 for 51 instances.
This variability is likely only noise and the actual longer times could simply be due to higher system load as opposed to pipewire API. While there is an overall upward trend in the measurements, it’s both quite small. With only a 40 ms increase in end to end latency on average and 70 ms or so between absolute min and max, we can conclude there is no big impact with regards to the number of clients. For measuring more detailed impacts another approach would be needed.
All raw results are available in results_v2024/test-case-4/
, including a list of
means test-case-4-list-means.txt
, a list of every measure
test-case-4-list-results.txt
and a capture of the output test-case-capture1.wav
.
To facilitate comparison with the previous evaluation, the two graphs from both
evaluation (Apertis v2023 & Pipewire 0.3.59
versus Apertis v2024 & Pipewire 0.3.84
)
are placed side by side.
\ \begin{figure}[!h] \captionsetup[subfigure]{labelformat=empty} \begin{subfigure}[t]{0.6\textwidth} \caption{Apertis v2023 & Pipewire 0.3.59} \end{subfigure} \hfill \begin{subfigure}[t]{0.4\textwidth} \caption{Apertis v2024 & Pipewire 0.3.84} \end{subfigure} \end{figure}
Between both graphs, we see the same increasing trend and the same order of magnitude for latency range ~ 30/40 ms. The maximum is same in both evaluation (1.54 with pipwire 0.3.59 and 1.52 with pipewire 0.3.84), and interestingly there seems to be an improvement in the lowest latency observed. In our previous evaluation, the lowest time to play the file was arround 1.49 secs whereas here we have several observations at 1.45 secs and 1.46 secs. To conclude, the new evaluation shows a small improvements regarding the lowest latency achieved in this test.
Test case 5: xrun behaviour under load
In this test the same setup as Test case 1 will be used. To test the xrun behavior the CPU availability for pipewire will be constrained such that it actually triggers xruns. The goal here is to evaluate how pipewire responds to that situation and whether/how it recovers properly when CPU becomes available again.
Results
stress-ng
has completed its run in 120.55s (2 min, 0.55 secs) (for a requested
timeout of 1 min). This run was performed with --class cpu --all 20
, that means
20 instances of all tests from the cpu
class were running simultaneously. The
aim was to trigger xruns to check how pipewire responds and how it recovers
properly after the load. Before starting to stress the CPU, pw-top
reported
few xruns for the different pipewire objects, while after the stressful period,
we see a small increase of xruns accross our nodes:
- +3 xruns for alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo
- +0 xrun for alsa_output.platform-sound.stereo-fallback
- +1 xrun for my-sink-A
- +2 xruns for my-sink-B
- +3 xruns for jackplay
- +2 xruns for jackplay (our second instance of sndfile-jackplay)
This is relatively small compared to the +548 xruns we had in the previous evaluation. Interestingly, in the previous evaluation, increasing the system load had the effect of generating xruns in only one node whereas here xruns are distributed accross nodes.
In the captured streams, test-case-capture1.wav
from the USB sound card
and test-case-capture2.wav
from the built-in sound card (both available in
results_v2024/test-case-5/
), we begin to hear the effects of the load at 0:13
until 2:13. At the beginning of load increase (i.e. 0:13), in both captures
we hear several cracking sounds, then the streams become smooth with rares and
isolated cracking until end of system load. At the end of system load (i.e. 2:13),
we hear again several cracking sounds for a few secondes, then streams become
smooth again. The streams are mainly affected only at the beginning and at the
end of the stressful period while during this period pipewire is able to maintain
a constant stream with a certain quality.
Compared to the previous evaluation, the quality of the stream provided by pipewire is greatly improved during the simulation of a high system load. In the previous evaluation, a stream was stopped whereas here the stream continues with only small artefacts.
Results summary (comparing results to the previous evaluation)
- Test case 1: Compared to the previous evaluation, there is no change in the results: streams are smoooth and no artefacts are audible when the switch happens.
- Test case 2: After adjusting the test script to the new pipewire behavior, results are similar to the previous evaluation: only the reconfigured stream is affected as we have to make the input device idle to force pipewire to apply the changes. The direct or unrelated outputs are not affected themselves by the reconfiguration.
- Test case 3: In this test, results are improved as we don’t hear sound artefacts due to mixing streams like in the previous evaluation. Streams are smooth without hatching sound or loud cracking noise. We can hear few light cracking, but this probably only due to the microphone saturation because the microphone is not the best quality.
- Test case 4: Results are similar to the previous evaluation in terms of variability, latency ranges observed, etc. Tests seem to show a small improvement regarding the lowest time required to play the file.
- Test case 5: Pipewire’s streams are less affected by a high system load than in our previous evaluation. Now, pipewire is able to maintain smooth streams with only small cracking sounds whereas in the previous evaluation streams were strongly affected to the point of stopping a stream.
From a subjective point of view, the sound quality was improved between both version. While doing these tests, the sound had less crackling and was smoother overall.