SetBfree Hammond Organ (7)

Een Hammond Organ voor iedereen!

Tekst: Marjan Waldorp
Artikel uit Turning Wheel 2025-2

In de vorige aflevering hebben we gekeken naar soundservers, en in het bijzonder naar PulseAudio en de Jack Audio Connection kit. In deze aflevering gaan we kijken naar Pipewire, een nieuwe multi-media server voor video en audio streams. Na de Linux Audio Conference 2010 schreef Lennart Poettering (de hoofdontwikkelaar van PulseAudio) nog dat PulseAudio en Jack niet snel samengevoegd zouden worden, omdat ze totaal verschillende werelden bedienen! Jack is ontworpen voor professioneel geluid, waar lage latencies en hoge kwaliteit essentieel zijn. PulseAudio is ontworpen voor consumenten toepassingen, waar het dynamisch configureren van hoge latencies belangrijk is om stroom te sparen bij accu-gevoede apparaten zoals notebooks en telefoons. PulseAudio en Jack zijn toegesneden op hun taak en kunnen elkaar niet vervangen. 1 Maar Pipewire heeft beide werelden uiteindelijk dus toch samengebracht in één multi-media server!

De motivatie voor het ontwikkelen van Pipewire was om videodevices, zoals een camera, te kunnen delen met meer applicaties. Een “PulseVideo” server, die net zoals PulseAudio bemiddelt tussen de applicaties en de hardware devices. In 2017 begon Wim Taymans (de ontwikkelaar van Pipewire) met het implementeren van audio in Pipewire. Taymans consulteerde diverse hoofdrolspelers in de Linux audiowereld, waaronder Paul Davis (ontwikkelaar van Jack) en Robin Gareus (Ardour muziekstudio), die we ook kennen als mede-ontwikkelaar van SetBfree. De software werd ingrijpend herzien en in 2021 werd Pipewire voor het eerst uitgebracht in Fedora Linux als vervanging voor PulseAudio.


Pipewire architectuur

Pipewire is gebaseerd op de patch-panel architectuur van Jack, waar applicaties en devices in een keten aan elkaar verbonden kunnen worden. Zo’n keten wordt een “graph” (diagram) genoemd. In Pipewire kunnen meer parallelle graphs worden opgezet. Vergelijkbaar met Jack en PulseAudio werkt de pipelining in Pipewire via een “zero-copy” mechanisme. Hierbij wordt - i.p.v. de bufferdata fysiek te copiëren - een pointer naar de bufferdata in RAM doorgegeven aan de ontvangende node. Zero-copy werkt sneller en verlaagt de cpu-belasting. Voor audio gebruikt Pipewire standaard het frameformaat van Jack (32-bit floating point), maar ook andere frameformaten worden ondersteund. Met AAC (compressed audio) kunnen bijv. frames zonder decompressie direct naar een Bluetooth device gestuurd worden. 2

Pipewire wil zowel Jack applicaties (lage latency) als PulseAudio applicaties (hoge latency) ondersteunen. Het timing systeem van Jack is hiervoor ongeschikt. Jack maakt gebruik van de gangbare methode in een computersysteem, waarbij een device via een hardware interrupt het besturingssysteem activeert, wanneer het device klaar is om nieuwe data te ontvangen (output) of data ter verwerking heeft (input). Jack gebruikt de device interrupt om de scheduling van de client applicaties in de graph te starten. Bij Jack loopt de executie van de clients dus synchroon met de verwerking van de data door de soundcard. De Jack cyclustijd (period) is gelijk aan de device interrupt intervaltijd. Tijdens de startup van Jack worden het interrupt interval en de grootte van de framebuffer van het device ingesteld en kunnen daarna niet worden gewijzigd.

Om een dynamische timing en daarmee latency mogelijk te maken, gebruikt Pipewire net als PulseAudio een kernel timer voor de executie van de clients. Door de kernel timer te wijzigen, kan de cyclustijd van de graph en daarmee de latency dynamisch worden aangepast. In Pipewire kan een tijdkritische applicatie, zoals SetBfree, een latency van 15ms vragen en een MP3 audio speler een latency van 1000ms. De consequentie van het ontkoppelen van graph- en device timing is dat er een retiming/resampling mechanisme nodig is om frames naar een device te kunnen sturen, vice versa frames van een device in een graph te kunnen verwerken. Pipewire gebruikt hiervoor het concept van de “Zita ALSA to Jack bridge”. (De Jack architectuur ondersteunt maar 1 device! Met de Zita-ajbridge kan een tweede ALSA device als client aan Jack gekoppeld worden.) 3

Net als Jack heeft Pipewire geen ingebouwd session management. Pipewire zelf biedt alleen de infrastructuur om verbindingen te leggen tussen applicaties en devices. Het is de session manager, die de verbindingen tot stand brengt. Ook is de session manager verantwoordelijk voor het detecteren van de sound hardware - vergelijkbaar met de “udev-detect” module in PulseAudio. De standaard session manager voor Pipewire is Wireplumber. Daarnaast zijn er grafische tools, zoals “qpwgraph”, waarmee de gebruiker eenvoudig de verbindingen kan beheren. 4

Net als PulseAudio start Pipewire automatisch, zodra een applicatie de socket (een soort device file) opent.


Pipewire installatie

Wanneer we Pipewire voor audio willen gebruiken, kunnen we het beste het pakket “pipewire-audio” installeren, dat PulseAudio volledig vervangt. PulseAudio zelf wordt automatisch verwijderd:

 # apt-get install pipewire-audio

Om ook Jack clients te kunnen ondersteunen, installeren we de Jack client libraries. Het hernoemen van de conf file in “0-pipewire-jack-aarch64-linux-gnu.conf” zorgt ervoor dat de pipewire shared libraries voorrang krijgen: 5

 # apt-get install pipewire-audio-client-libraries libspa-0.2-jack qpwgraph # cp /usr/share/doc/pipewire/examples/ld.so.conf.d/pipewire-jack-*.conf  /etc/ld.so.conf.d/ # cd /etc/ld.so.conf.d # mv pipewire-jack-aarch64-linux-gnu.conf  0-pipewire-jack-aarch64-linux-gnu.conf # ldconfig

Net als bij Jack is het aan te bevelen om Pipewire bij tijdkritische applicaties met “real-time” prioriteit te laten lopen. In Linux is het gebruik van real-time priority gekoppeld aan het lidmaatschap van een Linux groep. Bij Jack is dit de groep “audio”, bij Pipewire de groep “pipewire”. Tijdens de installatie van Pipewire wordt automatisch de groep “pipewire” gecreëerd en in “/etc/security/limits.d/95-pipewire.conf” de benodigde instellingen vastgelegd:

 @pipewire - rtprio 95 @pipewire - nice -19 @pipewire - memlock 4194304

We voegen de groep “pipewire” toe aan de betreffende gebruiker, bijv. “hammond”:

 # usermod -a -G pipewire hammond

Log uit en vervolgens opnieuw in om het groeplidmaatschap te effectueren!


PulseAudio en Jack verenigd

In Pipewire kunnen we PulseAudio- en Jack applicaties in één graph samen gebruiken. We kunnen dus bijv. met de VLC mediaplayer naar een instructievideo van Tony Monaco kijken en dit meteen op ons SetBfree Hammond orgel in praktijk brengen. (Zie screenshot Pipewire beheertool “qpwgraph”)


Default output device

Doordat Pipewire zowel een PulseAudio- als een Jack-interface (API) heeft, kunnen naast Pipewire specifieke tools diverse (niet alle) PulseAudio- en Jack tools gebruikt worden. Met “PulseAudio Volume Control” stellen we het profiel in op “Pro Audio”. (Profiel “Stereo Output” is alleen de HDMI uitgang.)

Met PulseAudio control stellen de default sink in:

 $ pactl list short sinks 55 alsa_output.platform-sound.pro-output-0 PipeWire s32le 8ch 48000Hz SUSPENDED 56 alsa_output.platform-sound.pro-output-1 PipeWire s32le 2ch 48000Hz SUSPENDED 57 alsa_output.platform-sound.pro-output-2 PipeWire s32le 32ch 48000Hz SUSPENDED $ pactl set-default-sink 56 $ pactl get-default-sink alsa_output.platform-sound.pro-output-1

We kunnen de default sink ook met Wireplumber control instellen:

 $ wpctl status Audio ?? Devices: ? 48. Built-in Audio [alsa] ? ?? Sinks: ? * 55. Built-in Audio Pro [vol: 1.00] ? 56. Built-in Audio Pro 1 [vol: 1.00] ? 57. Built-in Audio Pro 2 [vol: 1.00] $ wpctl set-default 56


Pipewire performance

Met “pw-top” kunnen we de live performance van de Pipewire graphs zien. Om een realistisch beeld te schetsen, spelen we een Midi-file af via SetBfree. In onderstaande screenshot zien we dat uitgang “pro-output-1” (DAC) ingesteld staat op een sample rate van 48kHz. De framebuffergrootte (quantum) is 1024 frames. De cyclustijd van de graph processing is te bepalen door het quantum te delen door de sample rate: 1024 / 48kHz = 21.3ms. 6 SetBfree heeft ca. 3ms (BUSY) nodig om zijn taak te completeren (waardes fluctueren). De totale processing tijd van de graph is 3.5ms (kolom “WAIT” pro-output-1). De belasting is dus laag: 3.5ms / 21.3ms = 0.16 (W/Q). (Zie voor uitleg van de velden: man pw-top)

Wanneer we voor het gehele SetBfree synthesizer systeem (van keyboard t/m speakers) een totale responstijd van ca. 20ms willen realiseren, is een graph cyclustijd (=buffertijd/latency) van 21.3ms te hoog. Om die doelstelling te halen, zouden we het quantum moeten halveren naar 512 frames. De buffertijd wordt dan: 512 / 48kHz = 10.7ms. Gezien de lage belasting van de Pipewire graph mag dat geen probleem zijn! We passen de Pipewire configuratie dus overeenkomstig aan door het systeem configuratiebestand naar de gebruikersomgeving te copiëren en vervolgens te wijzigen:

 $ mkdir ~/.config/pipewire $ cd ~/.config/pipewire $ cp /usr/share/pipewire/pipewire.conf .

In “pipewire.conf” wijzigen we het default quantum naar 512 frames:

 #default.clock.quantum = 1024 default.clock.quantum = 512

We effectueren de gewijzigde instelling door Pipewire te herstarten:

 $ systemctl --user restart pipewire.service

N.B.: Pipewire verwacht de locale configuratie te vinden in “$XDG_CONFIG_HOME/pipewire”. Mocht XDG_CONFIG_HOME niet gezet zijn, definieer deze variabele dan in “~/.xsessionrc”:

 export XDG_CONFIG_HOME="$HOME/.config"

Onderstaande screenshot toont de gewijzigde situatie. Tegelijk met SetBfree draait de VLC mediaplayer een video af. VLC is een zware applicatie, die bijna 30% CPU-belasting genereert! Kunnen we op de ODROID N2+ SetBfree laten lopen met een buffertijd van 10.7ms en tegelijk een video met VLC afspelen? Pipewire “pw-top” geeft uitsluitsel. VLC is een PulseAudio applicatie en wordt afgehandeld via de “pipewire-pulse” daemon. De “pipewire-pulse” daemon brengt een additionele cyclustijd/latency met zich mee van 1764 / 44.1kHz = 40ms. In de kolom “BUSY” zien we dat VLC ca. 300us (microseconde) nodig heeft om zijn taak te completeren. De impact op de Pipewire graph is dus opmerkelijk gering: 0.3ms / 40ms = 0.03! Het uitpakken van de audio data uit de MP4-file en het decoderen van de MP3/AAC data vraagt kennelijk relatief weinig tijd van de CPU. Het is dus vooral de video processing, die de CPU-belasting van VLC veroorzaakt! De belasting van SetBfree is nagenoeg gelijk aan de originele situatie: 1.8ms / 10.7ms = 0.17. De cyclustijd is gehalveerd, maar per cyclus hoeft SetBfree ook maar de helft van het aantal frames te genereren. De halvering van de cyclustijd verdubbelt weliswaar het aantal CPU context switches per seconde, echter dat weegt nog niet door in de Pipewire graph performance. Toch hebben we met een quantum van 512 frames de ondergrens van de latency bereikt! Wanneer we het quantum naar 256 frames verlagen, zien we overrun errors (XRUN) in de “ERR” kolom verschijnen en wanneer we het quantum op 128 frames instellen, horen we duidelijk de dropouts. Het geluid klinkt dan als een spetterende grammofoonplaat.


Wireplumber tweaking

Wanneer een in- of uitgang niet meer gebruikt wordt, zet Pipewire de betreffende node na een timeout in “SUSPENDED” state. Een script van de Wireplumber regelt dit (/usr/share/wireplumber/scripts/monitors/alsa.lua). De bedoeling hiervan is uiteraard om stroom te sparen. Bij het actief schakelen van de DAC-node horen we echter een forse plop uit de luidsprekers! Als stroomgebruik niet kritisch is, is er dus wat voor te zeggen om de node in “IDLE” state te laten staan. De timeout voor het in “SUSPENDED” state zetten wordt bepaald door de variabele “session.suspend-timeout-seconds”. Als we die variabele op “0” zetten, is de suspend-functie uitgeschakeld. Net als bij de Pipewire configuratie kunnen we de Wireplumber configuratie op gebruikersniveau wijzigen. Als eerste leggen we een lokale directory aan:

 $ mkdir -p ~/.config/wireplumber/wireplumber.conf.d

In deze directory creëren we een configuratiebestand met bijv. de naam “10-suspend.conf”:

 monitor.alsa.rules = [ { matches = [ { node.name = "~alsa_output.*" } ] actions = { update-props = { session.suspend-timeout-seconds = 0 } } } ]

Hierna herstarten we de Wireplumber:

 $ systemctl --user restart wireplumber.service

De betreffende output node blijft nu in “IDLE” state staan.


Pipewire/Wireplumber documentatie

  • Pipewire documentatie 7
  • ArchLinux Pipewire wiki 8
  • Wireplumber documentatie 9

  1. “PulseAudio and Jack”, Lennart Poettering 2010
    http://0pointer.de/blog/projects/when-pa-and-when-not.html

  2. Pipewire paper and presentation, Wim Taymans, Linux Audio Conference 2020
    https://lac2020.sciencesconf.org/

  3. http://kokkinizita.linuxaudio.org/linuxaudio/zita-ajbridge-doc/quickguide.html

  4. https://www.collabora.com/news-and-blog/blog/2020/05/07/wireplumber-the-pipewire-session-manager/

  5. https://wiki.debian.org/PipeWire

  6. https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/698#note_808894

  7. https://docs.pipewire.org/index.html

  8. https://wiki.archlinux.org/title/PipeWire

  9. https://pipewire.pages.freedesktop.org/wireplumber/