Eintrag von 2020-07-26
▶ RaspberryPi + mpd + pulseaudio + bluetooth = FOSS-Jukebox 2020
Im Laufe der Jahre habe ich schon einige "Jukeboxen" für mein Zuhause mithilfe des langgedienten Music Player Daemon (mpd) gebaut. Kürzlich hat es mich wieder einmal gereizt, einen Bluetooth-Lautsprecher auf diese Weise ans Heimnetzwerk zu bringen. Im Jahr 2020 gelingt das unter GNU/Linux nur noch via PulseAudio, da bluez (worüber Bluetooth-Peripherie aller Art angebunden wird) die direkte Unterstützung von ALSA vor einiger Zeit ad acta gelegt hat. Da sich der Gesamtaufbau aufgrund der recht komplexen Abhängigkeiten verschiedener Dienste und Technologien als nicht trivial erwiesen hat, will ich mein Setup hier öffentlich dokumentieren.
Plattform: RaspberryPi 3b + Arch Linux ARM
Bei vielen Bastlern fallen im Laufe der Jahre wohl überzählige RaspberryPi Single Board Computer der einen oder anderen Generation an - so auch bei mir. Das war der Hauptgrund, warum ich diese Plattform als Host für meine Jukebox gewählt habe. Als Zuckerl gibt es ein für den RPi vorkompiliertes Release meines Lieblingswebfrontends für mpd, ympd. Da mir die Raspbian-Images der RPi Foundation auch in der Minimalvariante immer noch etwas zu viel an für mich unnotwendiger Software mitbringen, greife ich auf dem RPi gerne zu Arch Linux ARM, das sich als gutes Pendant zu meiner bevorzugten Distribution für meinen Desktop erwiesen hat.
Da ich mit der Inbetriebnahme des im SoC integrierten Bluetooth-Adapters nicht auf Anhieb zum Erfolg gekommen bin, habe ich kurzerhand einen noch in einer Lade herumliegenden Digitus USB-Bluetooth-Adapter mit Support für Bluetooth 4.0 herangezogen, und sowohl WiFi- als auch BT-Interface des RPi per /boot/config.txt deaktiviert. Die Verbindung zum LAN stellt mein RPi folglich via Ethernet her, und dank systemd-networkd ist der DHCP-Support für IPv4 im Nu eingerichtet.
Bluetooth
Im ersten Schritt installieren wir die für die bluetooth-Kommunikation notwendigen Pakete: `pacman -S bluez-utils bluez`. Ist das erledigt und ist der bluetooth-Support kernelseitig korrekt initialisiert (wenn ja, existiert ein Directory namens /sys/class/bluetooth), kann man mit `systemctl enable --now bluetooth` sicherstellen, dass die zugehörigen Userspace-Komponenten verfügbar sind und bleiben. Mit dem so gestarteten bluetoothd interagiert man fortan via bluetoothctl, um BT-kompatible Geräte zu verbinden und zu verwalten. Damit das möglich wird, muss man den Adapter aber erst aus seinem initalen Dornröschenschlaf erwecken: `bluetoothctl power on && bluetoothctl scan` sorgt dafür, dass sich der Adapter auf seinem Frequenzband umzuhören beginnt, ob es paarungswillige Geräte in der Nachbarschaft gibt. Jetzt muss man dafür sorgen, dass die Peripherie - in meinem Fall ein Anker SoundCore 2-Lautsprecher - im Pairing Mode ist (Details dazu findet man im Handbuch des zu verbindenen Geräts), und irgendwann unter bekanntgabe seiner Hardware-Adresse im Output vorbeiscrollt.
Hat man die HWaddr gefunden, kann man das so identifizierte Gerät pairen und dieser Gegenstelle permanent vertrauen, was ein automatisches Wiederverbinden im Falle des Verbindungsabbruches ermöglicht. Das tut man (hier gezeigt anhand einer exemplarischen Beispieladresse) so: `bluetoothctl pair 00:00:5E:00:53:12 && bluetoothctl trust 00:00:5E:00:53:12`. Ein finales `bluetoothctl connect 00:00:5E:00:53:12` sollte nun dafür sorgen, dass über die Verbindung zur Gegenstelle tatsächlich eines der angebotenen Services beansprucht wird, die man via `bluetoothctl info 00:00:5E:00:53:12` enumerieren lassen kann. Bei meinem Lautspreche sieht die Ausgabe des obigen info-Aufrufes so aus:
[root@jukebox ~]# bluetoothctl info 00:00:5E:00:53:12 Device 00:00:5E:00:53:12 (public) Name: SoundCore 2 Alias: SoundCore 2 Class: 0x00240404 Icon: audio-card Paired: yes Trusted: yes Blocked: no Connected: yes LegacyPairing: no UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb) UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb) UUID: Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb) UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb) UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb) UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb) Modalias: usb:v099Ap0500d011B
Im künftigen Betrieb will man freilich nicht nach jedem Neustart des Hosts oder der BT-Peripherie dafür Sorge tragen, dass die beiden Stationen einander wiederfinden und Kontakt aufnehmen können - weswegen wir sicherstellen, dass der bluetoothd uns diese Arbeitsschritte abnehmen wird. Dafür erweitern wir die bluetooth.service-Unit um zwei ExecPost-Stanzas in einer Datei namens /etc/systemd/system/bluetooth.service.d/override.conf (via `systemctl edit bluetooth.service` wird diese Struktur automatisch angelegt):
[Service] ExecStartPost=/usr/bin/bluetoothctl power on ExecStartPost=/usr/bin/bluetoothctl agent on
Mit diesen Konfigurationsbausteinen an Ort und Stelle sollten Lautsprecher und RPi immer dann automatisch Kontakt zueinander aufnehmen, wenn beide eingeschaltet und der Lautsprecher nicht mit einem anderen Gerät verpaart ist.
PulseAudio
PulseAudio ist eine Technologie, die zumindest in den ersten Jahren ihrer Existenz sehr umstritten war. Ich möchte diesen flexiblen Soundserver am Desktop schon seit vielen Jahren nicht mehr missen, da ich Features wie das Umrouten von Audio-Quellen auf andere/gerade neu im System registrierte Audiogeräte sehr schätze und oft benutze. Auch auf einem System, das ohne graphische Session läuft, kann man PulseAudio einsetzen - aber es ist durchaus etwas mühsam, da viele sonst "automagisch" funktionierende Dinge bewusster und von Administratorhand eingerichtet werden müssen. PulseAudio selbst bemüht sich auch redlich, den Einsatz als "system-wide instance" in der Dokumentation unattraktiv zu machen. Wie auch immer - in diesem speziellen Fall bleiben keine wirklichen Alternativen, und technisch möglich ist es allemal.
Im ersten Schritt kümmern wir uns um die Installation des Pakets sowie darum, dass es die von PulseAudio stillschweigend erwarteten User und Gruppen gibt, um die Anforderungen des "system"-Modus zu befriedigen. Das erledigen die folgenden Kommandos: `pacman -S pulseaudio pulseaudio-bluetooth`, `useradd -M -c "pulseaudio system user" -d /var/run/pulse -r -s /bin/nologin pulse` und `groupadd -r pulse-access` (die Gruppe "pulse-access" ist in diesem Fall nur kosmetisch wichtig, aber ihr Vorhandensein spart uns ein Warning bei jedem Start von PulseAudio, was ich für einen akzeptablen Kuhhandel befinde).
Mit diesen neuen Accounts bewaffnet erstellen wir eine pulseaudio.service Unit für systemd, die in der Datei /etc/systemd/system/pulseaudio.service so aussieht:
[Unit] Description=PulseAudio system-wide sound server After=bluetooth.service Before=mpd.service [Service] Type=notify ExecStart=/usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal --disallow-exit RuntimeDirectory=pulse [Install] WantedBy=multi-user.target
Aufmerksame Leser werden sich wundern, dass wir hier kein User=- bzw. Group=-Stanza finden können. Das liegt daran, dass ein als root gestarteter pulseaudio-Server im "system"-Modus automatisch via setuid() auf pulse zurückschraubt. Der Parameter RuntimeDirectory=pulse sorgt dafür, dass systemd vor dem Start des Soundservers das Verzeichnis /run/pulse anlegt, das durch einen Symlink (/var/run -> ../run) als Home Directory für den Service-User taugt. (PulseAudio hat in Version 13 immer noch "/var/run/" als Prefix hartkodiert, weswegen ich diesen Kompatibilitäts-Pfad bewusst gewählt habe.)
Das zuvor explizit installierte bluetooth-Modul für PulseAudio ist in den meisten Distributionen nicht im Basispaket enthalten, und will auch deshalb von Hand in die Konfiguration eingebunden bzw. geladen werden. Um das in der "system"-Instanz von PulseAudio zu erreichen, erweitern wir die Datei /etc/pulse/system.pa an ihrem Ende um das folgende Stanza:
### Enable bluetooth audio support via bluez .ifexists module-bluez5-discover load-module module-bluez5-discover .endif
Ein beherztes `systemctl daemon-reload && systemctl enable --now pulseaudio.service` sorgt nun dafür, dass unser Soundserver zum Leben erwacht. Haben wir alles richtig gemacht, präsentiert sich der Status des Services in etwa so:
# systemctl --no-pager -l status pulseaudio ● pulseaudio.service - PulseAudio system-wide sound server Loaded: loaded (/etc/systemd/system/pulseaudio.service; enabled; vendor preset: disabled) Active: active (running) since Sun 2020-07-26 12:37:25 UTC; 1h 30min ago Main PID: 334 (pulseaudio) Tasks: 2 (limit: 2155) CGroup: /system.slice/pulseaudio.service └─334 /usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal --disallow-exit Jul 26 12:37:25 jukebox pulseaudio[334]: Running in system mode, forcibly disabling SHM mode. Jul 26 12:37:25 jukebox pulseaudio[334]: Running in system mode, forcibly disabling exit idle time. Jul 26 12:37:25 jukebox pulseaudio[334]: OK, so you are running PA in system mode. Please make sure that you actually do want to do that. Jul 26 12:37:25 jukebox pulseaudio[334]: Please read http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/WhatIsWrongWithSystemWide/ for an explanation why system mode is usually a bad idea. Jul 26 12:37:25 jukebox pulseaudio[334]: Failed to open cookie file '/var/run/pulse/.config/pulse/cookie': No such file or directory Jul 26 12:37:25 jukebox pulseaudio[334]: Failed to load authentication key '/var/run/pulse/.config/pulse/cookie': No such file or directory Jul 26 12:37:25 jukebox pulseaudio[334]: Failed to open cookie file '/var/run/pulse/.pulse-cookie': No such file or directory Jul 26 12:37:25 jukebox pulseaudio[334]: Failed to load authentication key '/var/run/pulse/.pulse-cookie': No such file or directory Jul 26 12:37:25 jukebox pulseaudio[334]: Failed to acquire org.pulseaudio.Server: org.freedesktop.DBus.Error.AccessDenied: Connection ":1.16" is not allowed to own the service "org.pulseaudio.Server" due to security policies in the configuration file Jul 26 12:37:25 jukebox systemd[1]: Started PulseAudio system-wide sound server.
Die Warnings wegen des "cookie files" und des "authentication key" zeugen davon, dass diese von PulseAudio beim Starten neu erzeugt worden sind, weil /var/run/pulse nur zur Laufzeit des Services existiert. Der DBus-Fehler rührt daher, dass es für die Unit keinen Session Bus (wie in einer Desktop-Session nach einem User-Login) gibt, in der sich PulseAudio registrieren könnte, was für unseren Einsatzzweck keine weitere Rolle spielt.
mpd
Die Konfiguration von mpd sowie die Interaktion mit dem Dienst über einen Client will ich hier nicht detailliert abhandeln - die Integration mit PulseAudio (der wiederum die Tonwiedergabe via Bluetooth für mpd transparent macht) aber schon. Zuerst müssen wir sicherstellen, dass der User mpd Mitglied der pulse-Gruppe ist, die beim Anlegen des pulse-Useraccounts automatisch erstellt wurde - andernfalls kann mpd nicht via Shared Memory mit pulseaudio kommunizieren. Dafür bietet sich das Editieren der group-Database z. B. via vigr an. Bei mir ist der mpd-User letztendlich wie folgt angelegt:
# id mpd uid=45(mpd) gid=45(mpd) groups=45(mpd),995(audio),975(pulse),974(pulse-access)
Zusammen mit einem entsprechenden (ggf. zusätzlichen) Output-Stanza in der /etc/mpd.conf genügt das auch schon, um mpd erfolgreich mit PulseAudio zu verdrahten:
### [...] audio_output { type "pulse" name "PulseAudio" } ### [...]
Startet man mpd.service nach dieser Aenderung bzw. Erweiterung der Konfigurationsdatei neu, findet dieser durch die PulseAudio-Client-Library automatisch zur systemweiten Instanz des Soundservers. Um zu verifizieren, dass mpd auch wirklich pulseaudio nutzt, können wir z. B. via lsof einen Blick in die geführten Dateideskriptoren werfen - was bei mir folgendes Bild ergibt:
[root@jukebox ~]# lsof -n -p $(pidof mpd) | grep pulseaudio mpd 285 mpd DEL REG 0,1 16985 /memfd:pulseaudio mpd 285 mpd mem REG 179,2 464332 662996 /usr/lib/pulseaudio/libpulsecommon-13.0.so mpd 285 mpd 18u REG 0,1 67108864 16985 /memfd:pulseaudio (deleted)
Diese Konstellation sollte dazu führen, dass mpd über den PulseAudio-spezifischen Output allen möglichen Lärm erzeugen kann, sobald ein Client ihn darum bittet.
direkter Link ▲© 2007-2020 Johannes Truschnigg | valid xhmtl & css