<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mathieu Réquillart on Medium]]></title>
        <description><![CDATA[Stories by Mathieu Réquillart on Medium]]></description>
        <link>https://medium.com/@mathieu-requillart?source=rss-1bed43070a36------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*zfS9kXl-DNyxEte3</url>
            <title>Stories by Mathieu Réquillart on Medium</title>
            <link>https://medium.com/@mathieu-requillart?source=rss-1bed43070a36------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 17 May 2026 10:05:31 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mathieu-requillart/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry Pi audio server I wanted — Flash Images and docs.odio.love]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-flash-images-and-docs-odio-love-5d27584cab57?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/5d27584cab57</guid>
            <category><![CDATA[sustainability]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[audio]]></category>
            <category><![CDATA[home-assistant]]></category>
            <category><![CDATA[open-home]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Mon, 13 Apr 2026 11:34:03 GMT</pubDate>
            <atom:updated>2026-04-14T23:33:42.812Z</atom:updated>
            <content:encoded><![CDATA[<h3>Part 14 — The bricks are laid. Here’s the house.</h3><p>Part 13 ended with the installer and a promise: a ready-to-flash SD card image. This article delivers that, and the documentation that ties everything together. The articles documented each feature. Each repository ships its own README. The flash image and <a href="https://docs.odio.love/">docs.odio.love</a> are what turns all of that into a coherent whole.</p><h3>Flash images</h3><p>The installer works. curl -fsSL https://beta.odio.love/install | bash on an existing system, five minutes on x86, fifteen on a Pi 3B+. But it still assumes SSH access, a running Debian, and someone comfortable with a terminal.</p><p>odio now ships as a flashable image through <a href="https://www.raspberrypi.com/software/">Raspberry Pi Imager</a>. Open Imager, go to Options &gt; Content Repository &gt; Use custom URL, enter https://beta.odio.love/odio.rpi-imager-manifest, pick your architecture (armhf or arm64), configure hostname and SSH in Imager&#39;s settings, flash, boot. Your Pi comes up with the full stack running.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/681/1*OWuzeRxOuRy8lvR_W_bq6Q.png" /></figure><p>Both paths produce the same result. Same packages, same services, same configuration. The image is built from the same Ansible playbook, frozen at release time. The curl installer remains for existing systems, upgrades, and non-Pi hardware.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YUtJR8nugdvNLToSaJaW1Q.png" /><figcaption>Dooz Kawa, Le savoir est une arme. RIP.</figcaption></figure><p>Since the installer shipped, the feedback has been consistent, and really encouraging. Someone wrote “<em>I have imagined such a thing for many years, and glad to see someone has finally put all the parts together.</em>” Another: “<em>This is the only project in the last 5 years that allowed me to recycle my FIRST Raspberry Pi 1 from 2012!!!</em>” A French user has a stereo amplifier that shipped with Spotify Connect, but the manufacturer stopped updating it. He switched to Qobuz and the amp became a paperweight. odio is on his to-do list to bring it back to life. Exactly what I tried to avoid when I first bought my Pi B+ in 2016.</p><p>These are three different people with three different setups, but the same underlying problem: hardware that still works, abandoned by the software that was supposed to run on it. That’s what odio is for. A pair of passive speakers from the 80s, a stereo amp from 2005, a receiver your parents bought before you were born: plug a Pi with a DAC into any of them and they become an AirPlay receiver, a Spotify Connect device, a Bluetooth speaker, a multi-room endpoint. You do need a Pi and a few peripherals (a DAC, maybe a Bluetooth dongle, optical drive, cables), but all of that is widely available and easy to find second-hand for a few euros. The hardware was never the problem. The missing piece was always the software. That’s what the flash image makes accessible to anyone with an SD card reader.</p><h3>Why it matters</h3><p>odio was carbon-positive from its very first user: me. All the R&amp;D, the development, the CI pipelines, the electricity, the AI-assisted coding sessions: I estimated the total carbon cost of developing odio at roughly 4 kg of CO2 (AI inference on US datacenters, GitHub Actions CI, and the marginal cost of my dev machine running on clean nuclear energy). That’s a rough estimate, and I’d welcome better data on AI inference energy costs if anyone has it.</p><p>That cost was paid back the moment I didn’t buy a commercial streamer. According to <a href="https://s29.q4cdn.com/969873633/files/doc_downloads/2023/03/Sonos-Era-100-Product-Environmental-Report.pdf">Sonos’s own environmental report</a> (ISO 14040/14044, third-party verified), a single Era 100 represents 166 kg of CO2e over its lifetime, of which about 42 kg is manufacturing alone. My Pi B+ was already in a drawer. Every new odio user who rescues an old Pi or avoids buying a disposable streamer does the same math. The development cost is fixed and already paid. The software duplicates for free. The hardware doesn’t.</p><p>The Pi is not the only old hardware here. My NAS is from <strong>2013</strong>. My HTPC too. My TV is from <strong>2014</strong>. They’re all still running the latest Debian, all still doing their job. The pattern is the same every time: a low-power, specialized machine, given one clear role, and kept alive by free software. The NAS stores and serves. The HTPC keeps my 2014 TV as a functional smart TV. The Pi plays music. None of them are powerful. All of them are sufficient.</p><p>Working on this project reminded me why I fell in love with computing in the first place. Free software, sharing knowledge, building tools that last. There are two models in this industry: one that treats hardware as consumable and software as leverage to sell more of it, and one that builds open tools designed to keep hardware alive. The Open Home Foundation builds open software and open hardware. odio does the same thing at the audio layer. That’s not a coincidence, that’s a shared set of values.</p><p>I went to <a href="https://sotoh.openhomefoundation.org/">State of the Open Home </a>last week in Utrecht, and I was really glad to be there. Hearing the talks, meeting community members who share the same concerns about freedom, privacy, and sustainability. The whole event was framed around building in the open and sustainability. That’s exactly what odio has been about since 2020, and how I’ve kept my multimedia setup running since 2013.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lh4-scBDaQlJTlLsr6fDJQ.jpeg" /><figcaption>Gift stickers for Sotoh attendees ♥️</figcaption></figure><p>I also heard about <a href="https://www.sendspin-audio.com/">Sendspin</a>, an open standard for synchronized multi-room audio: a proof of concept for a Sendspin on odio is on the roadmap.</p><h3>The documentation</h3><p>The Medium series tells the story of how odio was built, article by article, decision by decision, over six years. It was never meant to be reference documentation. If you want to understand why PulseAudio instead of ALSA, or why everything runs in a systemd user session instead of as root, the articles are the right place. They explain the reasoning.</p><p>But if you want to install odio and use it, you need something structured around what you’re trying to do, not the order I built it.</p><p><a href="https://docs.odio.love/">docs.odio.love</a> is that reference. It covers:</p><p><strong>Getting started.</strong> Installation (both flash and curl), how the stack works, the architecture.</p><p><strong>Control.</strong> The <a href="https://docs.odio.love/guides/embedded-ui/">embedded web UI</a> built into go-odio-api, the <a href="https://pwa.odio.love/">odio PWA</a> for managing multiple nodes from your phone, and the <a href="https://github.com/b0bbywan/odio-ha">Home Assistant integration</a> where each node appears as a full HA device with media players, Bluetooth controls, service switches, and power management, all updated in real time over SSE.</p><p><strong>Features.</strong> Each protocol documented independently: <a href="https://docs.odio.love/guides/bluetooth/">Bluetooth</a>, <a href="https://docs.odio.love/guides/airplay/">AirPlay</a>, <a href="https://docs.odio.love/guides/spotify/">Spotify Connect</a>, <a href="https://docs.odio.love/guides/dlna/">UPnP/DLNA</a> with Tidal and Qobuz, <a href="https://docs.odio.love/guides/snapcast/">Snapcast multi-room</a>, <a href="https://docs.odio.love/guides/audio-cd/">Audio CD</a>, <a href="https://docs.odio.love/guides/usb-flashdrives/">USB auto-play</a>, <a href="https://docs.odio.love/guides/network-audio/">PulseAudio TCP network audio</a>.</p><p><strong>Use cases.</strong> How your existing devices work with odio: your <a href="https://docs.odio.love/guides/use-case-desktop/">desktop</a> sending audio to an odio node or running go-odio-api itself, your <a href="https://docs.odio.love/guides/use-case-nas/">NAS</a> hosting the library while Pi nodes play, your <a href="https://docs.odio.love/guides/use-case-htpc/">HTPC</a> running Kodi and go-odio-api next to an odio node, your <a href="https://docs.odio.love/guides/use-case-phone/">phone</a> as a control surface. Plus integration guides for <a href="https://docs.odio.love/guides/navidrome/">Navidrome</a> and <a href="https://docs.odio.love/guides/music-assistant/">Music Assistant</a>, <a href="https://docs.odio.love/guides/extensions/">How to add extensions</a>, based on users feedback.</p><p><strong>API.</strong> The full <a href="https://docs.odio.love/api/overview/">go-odio-api reference</a>: MPRIS, PulseAudio, systemd, Bluetooth, power, Zeroconf, SSE events.</p><p><strong>Ecosystem.</strong> Multiple repositories, each documented independently because each is usable independently. go-odio-api is the core, but <a href="https://docs.odio.love/disc-player/overview/">go-mpd-discplayer</a> works without it, <a href="https://github.com/b0bbywan/odio-ha">odio-ha</a> works without the PWA, the installer works without the flash image. Bricks, not a monolith.</p><p>The articles tell the story. The docs answer the question.</p><p>odio is in open beta. Flash an image, run the installer, or just read the docs. If you test it on hardware I haven’t tested, <a href="https://github.com/b0bbywan/odios/issues">report it</a>.</p><pre>curl -fsSL https://beta.odio.love/install | bash<br><br>https://beta.odio.love/odio.rpi-imager-manifest<br><br>https://github.com/b0bbywan/odios/releases/tag/2026.4.0rc6</pre><p><a href="https://beta.odio.love/">odio.love</a> — Your Pi. Your music. No expiry date.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5d27584cab57" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry Pi audio server I wanted — One command install]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-one-command-install-d4b9fd450593?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/d4b9fd450593</guid>
            <category><![CDATA[home-assistant]]></category>
            <category><![CDATA[installer]]></category>
            <category><![CDATA[ansible]]></category>
            <category><![CDATA[audiophile]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Tue, 31 Mar 2026 23:02:17 GMT</pubDate>
            <atom:updated>2026-03-31T23:02:17.522Z</atom:updated>
            <content:encoded><![CDATA[<h3>Part 13 — From 13 articles to curl | bash</h3><h4>Six years of Raspberry Pi audio guides. One install command.</h4><p>In 2020, during the first COVID lockdown, I published a <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-introduction-650020d135e1">series of articles on Medium about turning a Raspberry Pi B+ into a complete audio server</a>. <strong>Bluetooth A2DP, PulseAudio TCP, MPD, DLNA </strong>via<strong> upmpdcli, AirPlay </strong>with<strong> Shairport Sync, Spotify Connect, Audio CD, USB auto-play, Snapcast multi-room,</strong> and eventually<strong> PipeWire</strong>. Thirteen articles, in total each one a dedicated feature, each one a pile of apt install, config files, udev rules, systemd units, and hard-won debugging.</p><p>It worked. The Pi B+ from 2014 is still running. It went through four major Debian upgrades without a reinstall. Total hardware cost including DAC, Bluetooth dongle, CD drive, case and cables: €164.</p><p>But let’s be honest: nobody is going to follow 13 articles to reproduce this setup.</p><h4>The gap between a guide and a product</h4><p>The articles are complete, and they’re in order for a reason. Follow them from Part 1 to Part 12 and you’ll get a working setup. But that’s still a lot of guides to follow by hand, each with config files to adapt to your hardware, your Debian version, your DAC. The series started on Buster, got updates for Bookworm, and some parts now have Trixie-specific adjustments. Configs that worked two years ago need changes that only show up in edit notes.</p><p>It works. It’s documented. But it’s a multi-hour manual process, and that’s a barrier most people won’t cross.</p><h3>odios: the installer</h3><p><a href="https://github.com/b0bbywan/odios">odios</a> is the answer. One command:</p><pre>curl -fsSL https://beta.odio.love/install | bash</pre><p>It installs and configures the entire stack: MPD, PulseAudio, Shairport Sync, Snapcast, upmpdcli, BlueZ, spotifyd, go-odio-api, go-mpd-discplayer. It prompts for a target user, creates it if needed, backs up existing configs if they exist, detects the architecture, and runs.</p><p>Under the hood, it’s an Ansible playbook vendored into a self-contained tarball. Vendored, because resolving dependencies at install time on a user’s machine is a failure mode, not a feature. Ansible, because idempotent: install and upgrade are the same operation. You can re-run it after a failed network timeout and it picks up where it left off. The same playbook will generate the flash image when it ships.</p><p>The playbook barely touches the system. Root-level operations are limited to apt packages, enabling linger for the target user, and a couple of D-Bus and polkit policies. Everything else happens in user space: PulseAudio, MPD, Shairport Sync, go-odio-api, Snapcast, spotifyd, go-mpd-discplayer all run as systemd user services, configured under ~/.config. The installer is a guest on your system, not a landlord.</p><p>Five minutes on x86. Fifteen on a Pi 3B+. Up to an hour twenty on a Pi B+ at 800 MHz, but then again, that board is twelve years old and it still gets the job done.</p><figure><img alt="odio network architecture" src="https://cdn-images-1.medium.com/max/1024/1*vLw26hNSvxgxHoZWUr_5pw.png" /><figcaption>odio network architecture</figcaption></figure><h3>What changed since the articles</h3><p>The biggest shift is that bash scripts became Go binaries. The CD auto-play that used to be a udev rule chaining a systemd unit chaining a bash script calling mpc is now <a href="https://github.com/b0bbywan/go-mpd-discplayer">go-mpd-discplayer</a>: a single daemon that watches for disc and USB insertion, fetches metadata from GnuDB and MusicBrainz, generates CUE sheets, and feeds MPD. The Bluetooth pairing that relied on PIN-based authentication and a pile of bash is now replaced by commercial like pairing handled natively through BlueZ D-Bus APIs.</p><p>Those binaries became apt packages, built in CI, published to a dedicated apt repository. A tagged release on GitHub triggers the build pipeline. The package lands in the repo. apt upgrade sees it. No more copy-pasting configs from a blog post.</p><h3>go-odio-api: the layer that was always missing</h3><p>The articles covered each service individually. What didn’t exist was a way to control them as a whole. <a href="https://github.com/b0bbywan/go-odio-api">go-odio-api</a> is a REST API written in Go that bridges the entire stack into a single coherent interface. Playback, volume, Bluetooth pairing, systemd services, power management, output routing: all exposed as HTTP endpoints, with SSE for real-time state updates.</p><p>It works because of one architectural decision made back in 2020: running everything in a systemd user session. Not root. go-odio-api actually refuses to start as root. From the user session, it has legitimate access to the D-Bus session bus, PulseAudio, MPRIS, BlueZ, logind. No privilege escalation, no setuid. The UNIX model, nothing more.</p><figure><img alt="odio internal architecture" src="https://cdn-images-1.medium.com/max/838/1*eQ9xu_fssIfR0FzZM7-sdg.png" /><figcaption>odio internal architecture</figcaption></figure><p>This is also why there’s no source switching. PulseAudio in the user session is the mixer. MPD, Shairport Sync, Spotify, Bluetooth, Snapcast: they all output to the same PulseAudio sink simultaneously. You don’t select a source. You just play.</p><p>The API makes everything else possible. <a href="https://pwa.odio.love">odio-pwa</a> is a Svelte progressive web app that controls all your odio nodes from any browser. <a href="https://github.com/b0bbywan/odio-ha">odio-ha</a> is a native Home Assistant integration where each node appears as a full HA device: media players, Bluetooth controls, service switches, power management, all as native entities, all updated in real time over SSE.</p><p>The API is the product. The clients are replaceable. Build yours.</p><h3>The result</h3><p>You run the command. When it finishes, your Pi is an AirPlay receiver, a Spotify Connect device, a Bluetooth speaker, a CD player with cover art and metadata, a UPnP/DLNA renderer with Qobuz and Tidal, a Snapcast multi-room client, a PulseAudio network sink for any Linux machine, and a Home Assistant device you can automate. No account. No cloud. No subscription. After six years without a reinstall, I changed the Pi B+ SD card and ran the same curl | bash. Everything came back up. It&#39;s been running daily since.</p><h3>What’s next</h3><p>A ready-to-flash SD card image is coming, built from the same Ansible playbook frozen at release time. PipeWire as an experimental audio backend option is in the pipeline. And odio is in open beta: test it on your hardware, <a href="https://github.com/b0bbywan/odios/issues">report issues</a>, and help grow the compatibility list.</p><p>Everything is open source under MIT or BSD 2-Clause. The full guide series is still on <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-introduction-650020d135e1">Medium</a> for anyone who wants to understand each brick. But now there’s a faster path.</p><pre>curl -fsSL https://beta.odio.love/install | bash</pre><p><a href="https://beta.odio.love/">odio.love</a> — Your Pi. Your music. No expiry date.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4b9fd450593" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry Pi audio server I wanted — 2026 Edition: Bluetooth & Audio CD]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-2026-edition-bluetooth-audio-cd-f5dc8a802572?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/f5dc8a802572</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[audio-cd]]></category>
            <category><![CDATA[bluetooth-speaker]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 01:27:43 GMT</pubDate>
            <atom:updated>2026-04-06T22:40:46.318Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Part 11 — What changed since 2020</em></p><p>In 2020, I published a series of articles about turning a Raspberry Pi B+ into a full-featured audio server. Two of them got the most engagement: <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-bluetooth-64c347ee0d22">Bluetooth</a> (Part 1) and <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-audio-cd-f985e8bd832c">Audio CD</a> (Part 6).</p><p>Both guides worked. I used that exact setup for years. But I also listed their limitations at the time: no CD metadata, no cover art in MPD clients, a PIN-based Bluetooth pairing model that modern devices increasingly ignore, and a setup process involving udev rules, systemd units, and bash scripts spread across multiple files that probably discouraged more than a few readers from trying.</p><p>Six years later, I’ve rewritten both from scratch in Go. This article covers what changed, why, and how to set it up today.</p><h3>Audio CD: from bash scripts to go-mpd-discplayer</h3><h3>What the 2020 setup looked like</h3><p>The original approach chained three components:</p><ol><li>A <strong>udev rule</strong> watching /dev/sr0 for audio disc insertion</li><li>A <strong>systemd oneshot service</strong> to avoid udev timeout issues</li><li>A <strong>bash script</strong> (mpc-cd.sh) that counted tracks with udevadm info, added them one by one to MPD as cdda:///1, cdda:///2, ..., and set the disc speed with eject</li></ol><p>It worked reliably. Insert a CD, it plays. Eject, it cleans up the queue.</p><p>But it had two real limitations I acknowledged in the article:</p><p><strong>No metadata.</strong> MPD showed cdda:///1, cdda:///2 in the client. No artist, no album, no track names. I wrote at the time: <em>&quot;What annoys me more is that I don&#39;t have tracks tags in M.A.L.P.&quot;</em> I knew CDDB could solve it but I didn&#39;t know how to feed the metadata back to MPD.</p><p><strong>Too many moving parts to set up.</strong> A udev rule file, a systemd service file, a bash script with hardcoded paths and device names, MPD configuration changes — and all of this had to be adapted if your device path, MPD port, or MPD instance was different from mine. Same deal with USB really.</p><h3>What replaced it: go-mpd-discplayer</h3><p><a href="https://github.com/b0bbywan/go-mpd-discplayer">go-mpd-discplayer</a> is a single Go daemon that replaces all of the above. It handles both audio discs and USB sticks.</p><p><strong>Disc detection</strong> is done natively through libgudev bindings in Go. The daemon watches for block device changes directly, no more udev rules needed.</p><p><strong>Metadata is solved.</strong> When a disc is inserted, my <a href="https://github.com/b0bbywan/go-disc-cuer">go-disc-cuer</a> library reads the disc ID via libdiscid, queries GnuDB and MusicBrainz for metadata, and generates a CUE file with artist, album, and track names. MPD&#39;s CUE playlist plugin reads this file and exposes the full tracklist with proper tags in any MPD client. Cover art works too — MPD clients can display album covers through the CUE playlist&#39;s as_folder option.</p><p><strong>USB sticks are handled by the same daemon</strong>. The daemon supports both native MPD mounting and symlink-based mounting for MPD servers that don’t support the neighbors plugin.</p><p><strong>Extras</strong> that weren’t possible with the bash approach: audio notifications on insert/eject (via PulseAudio or ALSA), configurable disc speed management, automatic MPD reconnection if the connection drops, and a built-in cron scheduler for automated playback (start a webradio at 6:30 on weekdays, play a CD on Saturday morning, etc).</p><h3>Setup</h3><p>go-mpd-discplayer requires a few C libraries at runtime and their dev counterparts to compile:</p><pre># Debian runtime + build dependencies<br>sudo apt install \<br>    libcdparanoia0 libdiscid0 libgudev-1.0-0 \<br>    libdiscid-dev libgudev-1.0-dev libasound2-dev</pre><p>Clone and build:</p><pre>git clone https://github.com/b0bbywan/go-mpd-discplayer.git<br>cd go-mpd-discplayer<br>go build -o mpd-discplayer<br>sudo mv mpd-discplayer /usr/local/bin/</pre><p>Install the systemd user service:</p><pre>sudo cp share/mpd-discplayer.service /usr/lib/systemd/user/<br>systemctl --user daemon-reload<br>systemctl --user enable --now mpd-discplayer</pre><p>MPD needs two additions in its config — the cdio_paranoia input plugin for disc playback, and the CUE playlist plugin for metadata and cover art:</p><pre>input {<br>    plugin &quot;cdio_paranoia&quot;<br>}<br>playlist_plugin {<br>    name &quot;cue&quot;<br>    enabled &quot;true&quot;<br>    as_folder &quot;true&quot;<br>}</pre><p>My configuration (~/.config/mpd-discplayer/config.yml):</p><p>yaml</p><pre>gnuHelloEmail: &quot;me@email.me&quot;<br>gnuDbUrl: &quot;http://uniqcode.gnudb.org&quot;<br>MPDConnection:<br>  Type: &quot;unix&quot;<br>  Address: &quot;/run/user/1000/mpd/socket&quot;<br>  ReconnectWait: 5<br>MountConfig: &quot;mpd&quot;<br>AudioBackend: &quot;pulse&quot;</pre><p>Using a unix socket for the MPD connection is recommended — it lets mpd-discplayer auto-discover MPD’s music_directory, which it needs to store CUE files and USB symlinks. With a TCP connection you&#39;d have to set MPDLibraryFolder manually.</p><p>That’s it. No udev rules to write. No systemd oneshot services. No bash scripts. Insert a CD — it plays with full metadata, track names, and cover art in your MPD client. Thanks to MPD devs for making this possible.</p><h3>Bluetooth: from PIN codes to remote controlled pairing</h3><h3>What the 2020 setup looked like</h3><p>The original guide configured the Pi as a Bluetooth audio sink using PulseAudio and a custom Python agent for pairing. The audio part — routing Bluetooth streams through PulseAudio to the HiFiBerry DAC — worked fine and hasn’t fundamentally changed since. Whether you use PulseAudio or PipeWire today (as I covered in <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-pipewire-tcp-server-b6016d9360c5">Part 10</a>), Bluetooth audio routing is handled the same way.</p><p>Configuration wasn’t easy and might have discouraged some once again. But what really didn’t age well is the pairing model.</p><p><strong>The PIN code is obsolete.</strong> The 2020 guide used a Python Bluetooth agent that prompted for a PIN on pairing. This was already becoming uncommon in 2020 — most modern phones and devices use “Just Works” pairing and simply don’t support PIN entry for A2DP audio connections. Over the years, more and more devices just failed to pair with this setup, especially iPhones.</p><p>It took me a quite some thinking to find a solution that would be as user friendly as the pin code was. I ended up copying, the usual flow offered by commercial speakers, and maybe enhancing it.</p><h3>What replaced it: Odio’s Bluetooth backend</h3><p><a href="https://github.com/b0bbywan/go-odio-api">go-odio-api</a> v0.7.0 turns the Pi into something that behaves like a real commercial Bluetooth speaker — the kind you just tap “connect” on from your phone.</p><p><strong>Pairing works like any Bluetooth speaker you’d buy.</strong> From the Odio web UI, tap the pairing button. A 60-second window opens that accepts any incoming connection — no PIN, no confirmation on the Pi. Your phone sees the device name (configured in BlueZ), taps connect, done. After 60 seconds, the pairing window closes automatically. Exactly like a JBL or a Sonos Roam.</p><p><strong>You can control the connected phone from the Pi.</strong> This is the part that was simply impossible before. Once a phone connects via Bluetooth, it appears in the Odio UI as a media player — with the current track, artist, album art, play/pause, next/previous, and volume. This works through Bluetooth AVRCP and MPRIS: Spotify, YouTube, BubbleUPnP, any app that exposes media controls over Bluetooth. Volume changes on the phone are reflected in real-time in the UI, and vice versa.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FmksVF9raH8s%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DmksVF9raH8s&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FmksVF9raH8s%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/359d9617fa82b3e0e77c6b5a67134b1f/href">https://medium.com/media/359d9617fa82b3e0e77c6b5a67134b1f/href</a></iframe><p>This video shows the full flow — powering on Bluetooth, pairing a phone that was never connected before, seeing it appear as a player with the current song, reporting commands from the phone, controlling playback and volume from the UI, and powering off.</p><p><em>The interface is responsive which might have been more impactul for the video with the complete flow, but screen recording on Gnome is so easy I didn’t think about recording my phone.</em></p><h3>Setup</h3><p>The system-level Bluetooth configuration is minimal. In /etc/bluetooth/main.conf:</p><pre>[General]<br>Name=Odio       # Bluetooth name shown during device discovery<br>Class=0x240428</pre><p>The Class value identifies the device as an audio receiver (Hi-Fi Audio + Headphones + Audio/Video). This is what makes your phone show the right icon and route audio correctly when connecting.</p><p>Make sure your user is in the bluetooth group and that the BlueZ service is running:</p><pre>sudo usermod -aG bluetooth $USER<br>sudo systemctl enable --now bluetooth</pre><p>On the Odio side, there’s nothing to configure — the Bluetooth backend is enabled by default. If Odio detects a BlueZ adapter, it exposes the pairing controls in the web UI and starts handling connections, and you can control every steps on Odio built-in UI. or the Odio API. Or soon on Home Assistant.</p><p>No Python agent. No PIN configuration. The audio routing itself — whether through PulseAudio or PipeWire — is unchanged from the 2020 setup. What Odio replaces is the pairing and control layer on top of it.</p><p><em>If you need audio notifications for bluetooth as in my video, you can still use the udev trick </em><a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-bluetooth-64c347ee0d22"><em>from my previous article</em></a><em>. Odio should eventually get its own audio notification system.</em></p><h3>The bigger picture: Odio</h3><p>Both of these replacements are part of a broader project. After years of running this setup with scattered scripts, multiple systemd services, SSH-based management, and fragile glue code, I built <a href="https://mathieu-requillart.medium.com/odio-introducing-the-missing-piece-to-unleash-linux-multimedia-f3a0baae4f5e">Odio</a> — a single Go daemon that exposes your entire Linux multimedia stack over HTTP.</p><p>Beyond Bluetooth, Odio handles:</p><ul><li><strong>MPRIS player control</strong> — auto-discovers all media players (Spotify, VLC, Firefox, MPD, Kodi) via D-Bus</li><li><strong>PulseAudio/PipeWire</strong> — global and per-app volume control, pure Go, no libpulse dependency</li><li><strong>systemd services</strong> — whitelist-based start/stop/restart of user services from your phone</li><li><strong>SSE event stream</strong> — real-time push updates, no polling</li><li><strong>Built-in web UI + </strong><a href="https://odio-pwa.vercel.app/"><strong>PWA</strong></a> — manage multiple machines from your phone</li><li><strong>Home Assistant integration</strong> — <a href="https://github.com/b0bbywan/odio-ha">odio-ha</a> (HACS custom component, SSE-based, no polling)</li></ul><p>It runs on my 10-year-old Raspberry Pi B+ at <strong>&lt;50ms p95</strong> response time,<strong> 0% CPU idle, 2% top</strong> under activity, <strong>&lt;20Mo RAM</strong>. Pre-built binaries and packages available for <em>amd64</em>, <em>arm64</em>, <em>armv7hf</em>, <em>armhf</em>, plus Docker multi-arch images on <em>ghcr.io/b0bbywan/go-odio-api:latest</em>.</p><p>No root. No cloud. No subscription. 100% local and free.</p><p><a href="https://odio.love">odio - Open-source Raspberry Pi audio streamer. Self-hosted, Home Assistant native.</a></p><h3>What’s next</h3><p>If you followed the original guide series, here’s where the 2026 stack stands:</p><ul><li><strong>Audio server</strong>: PulseAudio → PipeWire + pipewire-pulse (Part 10)</li><li><strong>Bluetooth</strong>: PIN agent → Odio Bluetooth backend</li><li><strong>Audio CD</strong>: udev + systemd + bash → go-mpd-discplayer</li><li><strong>USB sticks</strong>: udev + systemd + bash → go-mpd-discplayer</li><li><strong>Multiroom</strong>: not covered → Snapcast (Part 8)</li><li><strong>Remote control</strong>: SSH + scripts → Odio web UI + Home Assistant</li></ul><p>The parts that haven’t changed: MPD, shairport-sync, spotifyd, upmpdcli. They still work exactly as described in the original articles. The HiFiBerry DAC+ Pro still outputs to the same amplifier. The Pi is still in its wooden case.</p><p>What changed is everything around them — the glue, the control layer, the integration. That’s what was always missing, and that’s what Odio is for.</p><ul><li><strong>go-odio-api</strong>: <a href="https://github.com/b0bbywan/go-odio-api">https://github.com/b0bbywan/go-odio-api</a></li><li><strong>go-mpd-discplayer</strong>: <a href="https://github.com/b0bbywan/go-mpd-discplayer">https://github.com/b0bbywan/go-mpd-discplayer</a></li><li><strong>odio-ha</strong> (Home Assistant): <a href="https://github.com/b0bbywan/odio-ha">https://github.com/b0bbywan/odio-ha</a></li><li><strong>Full guide series</strong>: <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-introduction-650020d135e1">Introduction</a></li></ul><p><em>This is Part 11 of my Raspberry Pi audio server guide. The Pi B+ in the wooden case is now 10 years old, has been through 4 major Debian upgrades, and has never been reinstalled since 2020. Total cost including DAC, Bluetooth dongle, CD drive, RCA cable, power supplies and case: €164.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f5dc8a802572" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Odio: Introducing the Missing Piece to Unleash Linux Multimedia]]></title>
            <link>https://mathieu-requillart.medium.com/odio-introducing-the-missing-piece-to-unleash-linux-multimedia-f3a0baae4f5e?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/f3a0baae4f5e</guid>
            <category><![CDATA[home-assistant]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[multimedia]]></category>
            <category><![CDATA[dbus]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Sun, 15 Feb 2026 19:20:58 GMT</pubDate>
            <atom:updated>2026-04-06T22:42:05.486Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>For over a decade, I’ve kept old hardware alive on Linux: homemade NAS and HTPC since 2013, a Raspberry Pi B+ audio server running 24/7 since 2020 that <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-introduction-650020d135e1">I documented here</a>. Big thanks to the <a href="https://www.debian.org/index.fr.html">Debian</a> and <a href="https://www.openmediavault.org/">Openmediavault</a> projects to make this possible.</p><p>Like many, I built a multimedia setup I’m proud of. But cleanly integrating it into Home Assistant always felt hacky, with scattered integrations, SSH scripts, and fragile glue.</p><p><a href="https://github.com/b0bbywan/go-odio-api">go-odio-api</a> v0.4 is built for people like us. An ultra-lightweight Go daemon exposing a clean REST API to control everything in your Linux user session: MPRIS players (Spotify, VLC, Firefox, MPD), PulseAudio/PipeWire, systemd user services. No root. No hacks. Just Linux primitives.​</p><h3>The 3 Pillars (Modular Backends)</h3><h4>1. MPRIS Control (enabled by default)</h4><p>Lists all compatible players in real time: Spotify, VLC, Firefox, MPD (with mpd-mpris or mpDris2), etc.</p><p>Full actions: play/pause/stop/next/prev, volume, seek, shuffle/loop</p><pre><br>GET /players # instant inventory<br>POST /players/{player}/play<br>POST /players/{player}/volume # {“volume”: 0.5}<br>POST /players/{player}/seek # {“offset”: 1000000}</pre><h4>2. PulseAudio/PipeWire Management (enabled by default)</h4><p>Server info, client listing, volume/mute per client or general output in real time. Limited PipeWire support via pipewire-pulse</p><p>Endpoints:</p><pre><br>GET /audio/server<br>POST /audio/server/volume # {“volume”: 0.5}<br>GET /audio/clients<br>POST /audio/clients/{client}/mute </pre><h4>3. systemd USER Services (opt-in, security-critical, central to power)</h4><p>This backend is a real game-changer — and potentially the riskiest if I screw up the security. But it’s absolutely essential for home automation power users because you need to start/stop/restart services remotely: launching Kodi media centers, firing up Firefox kiosks for Netflix/YouTube, restarting pulseaudio, MPD or shairport-sync when they glitch. Here’s the idea.</p><p>How it works (explicit whitelist required):</p><pre>systemd:<br> enabled: true<br> user:<br>   — mpd.service # your music daemon (w/ MPRIS)<br>   — shairport-sync.service # AirPlay receiver (see my Medium article) <br>   — spotifyd.service # headless Spotify (needs MPRIS build)<br>   — firefox-kiosk@netflix.com.service # Netflix kiosk → auto MPRIS player<br>   — firefox-kiosk@youtube.com.service # YouTube kiosk → auto MPRIS player<br>   — kodi.service # full media center (w/ MPRIS addon)<br>   — vlc.service # VLC daemon<br> system: # monitoring only!<br> — bluetooth.service # watch system-wide BT status<br> — upmpdcli.service # UPnP renderer<br></pre><p>Endpoints:</p><pre>GET /services # ALL whitelisted units + real-time status<br>POST /services/user/mpd.service/start # launch music daemon<br>POST /services/user/kodi.service/restart # reboot media center <br>POST /services/user/firefox-kiosk@netflix.com.service/stop # kill Netflix kiosk</pre><h4>The Security Model</h4><p>Layer 1 is dead simple: systemd backend is disabled by default. You must explicitly set <em>systemd.enabled: true</em> AND provide a whitelist of units in config.yaml. No units listed = zero exposure. That’s the bare minimum.</p><p>The real protection: Odio enforces user-session mutations only through a an application-layer lockdown, regardless of your system configuration.</p><p>Let’s say you messed up your dbus policies through SSH trying to let regular users start system services, or your polkit rules are wide open, Odio’s will still refuses to execute the operation.</p><p>You can monitor system-wide units like bluetooth.service or upmpdcli.service (they show live status in GET /services), but never start/stop/restart them. For that, you’ll still need SSH and a CLI.</p><p>Remote Kodi restarts, Netflix kiosk launches, MPD service restarts. All possible without opening a giant systemd security hole. Application-layer enforcement makes it safe.<br>Other protections:</p><ul><li><strong>bind</strong>: <strong>localhost</strong> by default, <strong>single</strong> interface or <strong>all</strong> interfaces, exposing Odio is your choice.</li><li>Odio opens only the required sytemd bus based on config</li><li>Odio throughs a fatal error if you try to run it as root.</li></ul><p>There’s no authentication, yet, but Odio is meant for trusted LAN only anyway, not Internet exposure.</p><p>Without this backend secured properly, you’d be screwed for real home automation use cases. With it, I really hope every one can hack around safely.</p><h3>Why This Design?</h3><p>Odio is built around the principle of “dumb core, smart integrations” Unix style. The API stays deliberately simple, stable, and reliable. The real magic comes from the integrations.</p><h4>The User Session Bet</h4><p>The architecture makes a critical choice: all multimedia services run as systemd user units (systemctl --user), not system-wide daemons.</p><p>Why? Because this pattern has been a Linux best practice since systemd v208. Media services handle per-user resources: audio sinks, display sessions, D-Bus contexts, that belong in user space.</p><p><strong>This is the key unlock</strong>: by running everything in the user session, Odio talks to a single, unified D-Bus session bus. PulseAudio/Pipewire, MPRIS players (Spotify, VLC, Firefox, MPD, Kodi), user systemd units, all expose an interface on that bus.<br>Odio doesn’t need to know what Spotify is. Or VLC. Or your custom MPD setup. It just listens to the D-Bus session for MPRIS-compatible players and exposes them via HTTP. You add a new player? It shows up automatically. Zero code change. Zero config.<br>This is what makes Odio <strong>universal</strong> without being <strong>bloated</strong>.</p><h3>Test in 2 Minutes</h3><p>Build from source (no prebuilt binaries on purpose for now):</p><pre><br>git clone https://github.com/b0bbywan/go-odio-api<br>cd go-odio-api<br>go build -o odio-api .<br>./odio-api</pre><p>Or Docker (build your own image):</p><pre><br>git clone https://github.com/b0bbywan/go-odio-api<br>cd go-odio-api <br>docker build -t odio-api .<br>docker compose up</pre><p><strong>Immediate test:<br></strong>Start a youtube or vlc video, spotify or mpd player and test</p><pre>curl http://localhost:8018/players # your MPRIS players<br>curl http://localhost:8018/audio/clients # your Pulse clients<br>curl -X POST http://localhost:8018/players/mpd/pause # it works</pre><p><strong>Installation</strong>: systemd user service</p><pre><br># ~/.config/systemd/user/odio-api.service<br>[Unit]<br>Description=Odio API<br>After=sound.target network-online.target<br>Wants=sound.target network-online.target<br><br>[Service] <br>ExecStart=/usr/local/bin/odio-api<br>Restart=always<br>RestartSec=12<br><br>[Install]<br>WantedBy=default.target<br></pre><pre><br>systemctl --user enable --now odio-api.service</pre><h3>Integrations &amp; Roadmap</h3><p>Odio’s real power comes from integrations. The API is just plumbing infrastructure. The magic happens when you connect it to actual control interfaces.</p><h3>odio-ha: The Home Assistant Integration</h3><p>I’ve already built the first proper integration to prove what go-odio-api can do: <a href="https://github.com/b0bbywan/odio-ha"><strong>odio-ha</strong></a>.</p><p>It exposes:</p><ul><li><strong>Audio control</strong>: media_player.odio_audio_receiver for global PulseAudio/PipeWire volume/mute</li><li><strong>Per-service control</strong>: separate media_player per systemd service (mpd, kodi, shairport-sync, etc.) with power on/off, volume, state tracking</li><li><strong>MPRIS players</strong> (in progress): auto-discovered players (Spotify, VLC, Firefox, MPD) with full playback control and metadata</li></ul><p><strong>The real power — integration inheritance:</strong></p><p>Once odio-ha exposes your services, you can map them to native HA integrations. Example: MPD runs on your audio server (Odio manages the service), but you want rich features (playlists, album art). Just map the mpd odio service to your existing mpd integration when you set up Odio-ha Integration</p><ul><li>Odio handles service lifecycle (start/stop/restart)</li><li>HA’s MPD integration handles rich playback</li><li>Everything centralized on the <strong>right host</strong> — your media server</li></ul><p>Same for Kodi, Spotify Connect, Snapcast, or any player. <strong>Odio becomes the hub that makes all your HA integrations point to the correct machine.</strong></p><p>All local. No cloud. No SSH scripts. Automations and voice control just work.</p><p><strong>Current state: </strong>odio-apiv0.4 + odio-hav0.2.2 (add the repository in HACS) gives you clean control over <strong>audio</strong> and <strong>services</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tIgo7gBTHu0QJ2nmJeZmBg.png" /><figcaption>Odio Integration v0.3.0</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rg_ejpenyQtBhHpLbMRf-A.png" /><figcaption>Odio entity example</figcaption></figure><h3><strong>What’s next</strong></h3><p><strong>Bluetooth Backend (target v0.5):</strong> Turn your Linux box into a <strong>fully controllable Bluetooth speaker</strong>:</p><ul><li>Toggle Bluetooth power, pairing mode via API</li><li>Accept connections from phones, tablets, laptops</li><li>Playback control and metadata exposed through MPRIS (play/pause/next/prev, track info)</li><li>The speaker appears as a media_player entity in Home Assistant via odio-ha</li><li>Full remote control: no bluetoothctl/bt-agent/custom script hell</li></ul><p><strong>Fake a commercial Bluetooth speaker, but with full API control</strong></p><p><strong>Built-in dashboard (target v0.6)</strong>:</p><p>Simple web UI served by go-odio-api itself on <a href="http://localhost:8010/ui/">http://localhost:8018/ui/</a>. I don’t need to tell more, just let screenshot do the talking. There’s a good chance it becomes my HTPC homepage at login.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t6UIblR4fQg9ck1TqvVDKg.png" /><figcaption>Dev UI version</figcaption></figure><p>PR are on the way !</p><p><strong>Later</strong>: SSE, Authentication, Wayland Remote Control, Photos Casting…</p><h3>Join the Project</h3><p>Odio is <strong>very early stage</strong>. I pushed the very first commit on January 25, 2026, barely three weeks ago. It’s a<strong> Proof of Concept </strong>and experiment with Claude Code that went much better than expected. Video players weren’t even in the initial scope.</p><p>v0.4.0 works out of the box, but there’s a long road ahead. <strong>Expect bugs.</strong> I’ve mobilized 12 years of Linux multimedia tinkering and 15 years of startup experience to turn that POC into production-grade software: low footprint and portability across architectures and distributions for my old hardware, increasing test coverage since I broke v0.1.2, exhaustive config/README, and solid debug logs for integration developement... I’m building this to be as universal and lasting as possible, not just for my use case, but for yours too. And I can’t do this alone.</p><p><strong>I need your help to keep this on the right path.</strong></p><p>Does it work on your setup? What breaks? What’s missing? What pain points does your current stack have that we could solve together?</p><p>Try it: <a href="github.com/b0bbywan/go-odio-api">github.com/b0bbywan/go-odio-api</a> - <a href="github.com/b0bbywan/odio-ha">github.com/b0bbywan/odio-ha</a><br>Run it on your hardware. Tell me what works and what doesn’t. Show me your setup. If you want to contribute code, even better. Go is a wonderful language for this use case.</p><p><strong>This is what I want to build</strong>: the multimedia remote platform Linux has been missing. Universal. Local. Free. Built on stable primitives that already work.</p><p>But it only becomes that if it solves <strong>your</strong> problems, not just mine.</p><p>Let’s build it together.</p><p><a href="https://odio.love">odio - Open-source Raspberry Pi audio streamer. Self-hosted, Home Assistant native.</a></p><p><em>Fun fact: no me di cuenta de que “Odio” significa hate en español, pero me encanta el nombre, y hasta hice que el número de puerto coincidiera con él, así que lo mantendré.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f3a0baae4f5e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry pi audio server I wanted — PipeWire TCP server]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-pipewire-tcp-server-b6016d9360c5?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/b6016d9360c5</guid>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[pipewire]]></category>
            <category><![CDATA[audio-network]]></category>
            <category><![CDATA[bluetooth-speaker]]></category>
            <category><![CDATA[pulseaudio]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Thu, 01 Jan 2026 23:30:57 GMT</pubDate>
            <atom:updated>2026-01-01T23:30:57.018Z</atom:updated>
            <content:encoded><![CDATA[<h3><a href="https://medium.com/p/650020d135e1/edit">My ultimate guide to the Raspberry pi audio server I wanted</a> — PipeWire TCP server</h3><h3>Part 10 — Replacing a PulseAudio server with PipeWire</h3><h4>Introduction</h4><p>In a previous article, I explained how to <a href="https://medium.com/@mathieu-requillart/switching-pulseaudio-tcp-client-to-pipewire-7b7411a94c72">switch a PulseAudio TCP client to PipeWire</a>. This time, we’ll focus on the <em>server</em> side.</p><p>To be fully transparent, I haven’t yet migrated my main Raspberry Pi to <strong>PipeWire</strong> — I first tested everything on an older Raspberry Pi B with headphones to make sure nothing breaks. The goal here is simple: replace <strong>PulseAudio</strong> with <strong>PipeWire</strong> on the audio server without breaking anything in the existing setup.</p><p><strong>PipeWire</strong> itself is the audio engine. It does not speak the <strong>PulseAudio</strong> protocol by default. <strong>pipewire-pulse</strong> runs on top of <strong>PipeWire</strong> to implement the <strong>PulseAudio</strong> <strong>protocol</strong> and replace the <strong>PulseAudio</strong> <strong>daemon</strong>.</p><p>With <strong>pipewire-pulse </strong>:</p><ul><li><strong>PulseAudio</strong> clients will be able to communicate with PipeWire</li><li>TCP connections work exactly like before</li><li><a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-mpd-7ec04aabf91f">MPD</a>, <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-dlna-5b9dcd3c912e">DNLA</a>, <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-multiroom-support-with-snapcast-30e95fb74eb4">Snapcast</a>, and even <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-bluetooth-64c347ee0d22">bluetooth</a> keep working as before</li></ul><h4>1. Replace Pulseaudio server by Pipewire, pipewire-pulse and Wireplumber</h4><p>Let’s start by stopping <strong>PulseAudio</strong>, installing <strong>Pipewire</strong>, before definitely removing <strong>PulseAudio</strong></p><pre>$ systemctl --user disable --now pulseaudio.socket pulseaudio.service<br>$ sudo apt install pipewire-pulse pipewire wireplumber<br>$ systemctl --user enable --now pipewire.socket pipewire.service pipewire-pulse.socket pipewire-pulse.service wireplumber.service<br>$ sudo apt purge --auto-remove pulseaudio</pre><p>Then edit <em>/etc/pipewire/pipewire-pulse.conf </em>or <em>~/.config/pipewire/pipewire-pulse.conf.</em></p><p>The important parts in this file are:</p><ul><li>uncommented <em>“</em><strong><em>tcp:4713</em></strong><em>” </em>in <em>pulse.properties { server.address [ ] }</em></li><li>new <strong><em>{ cmd = “load-module” args = “module-zeroconf-publish” flags = [ ] }</em></strong> in <em>pulse.cmd = [ ]</em></li></ul><pre># PulseAudio config file for PipeWire version &quot;1.2.4&quot; #<br>#<br># Copy and edit this file in /etc/pipewire for system-wide changes<br># or in ~/.config/pipewire for local changes.<br>#<br># It is also possible to place a file with an updated section in<br># /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in<br># ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes.<br>#<br><br>context.properties = {<br>    ## Configure properties in the system.<br>    log.level       = 0<br>}<br><br>context.spa-libs = {<br>    audio.convert.* = audioconvert/libspa-audioconvert<br>    support.*       = support/libspa-support<br>}<br><br>context.modules = [<br>    { name = libpipewire-module-rt<br>        args = {<br>            nice.level   = -11<br>        }<br>        flags = [ ifexists nofail ]<br>    }<br>    { name = libpipewire-module-protocol-native }<br>    { name = libpipewire-module-client-node }<br>    { name = libpipewire-module-adapter }<br>    { name = libpipewire-module-metadata }<br>    { name = libpipewire-module-protocol-pulse<br>        args = {<br>        }<br>    }<br>]<br><br># Extra scripts can be started here. Setup in default.pa can be moved in<br># a script or in pulse.cmd below<br>context.exec = [<br>]<br><br># Extra commands can be executed here.<br>#   load-module : loads a module with args and flags<br>#      args = &quot;&lt;module-name&gt; &lt;module-args&gt;&quot;<br>#      ( flags = [ nofail ] )<br>pulse.cmd = [<br>    { cmd = &quot;load-module&quot; args = &quot;module-always-sink&quot; flags = [ ] }<br>    { cmd = &quot;load-module&quot; args = &quot;module-device-manager&quot; flags = [ ] }<br>    { cmd = &quot;load-module&quot; args = &quot;module-device-restore&quot; flags = [ ] }<br>    { cmd = &quot;load-module&quot; args = &quot;module-stream-restore&quot; flags = [ ] }<br>    { cmd = &quot;load-module&quot; args = &quot;module-zeroconf-publish&quot; flags = [ ] }<br>]<br><br>stream.properties = {<br>}<br><br>pulse.properties = {<br>    # the addresses this server listens on<br>    server.address = [<br>        &quot;unix:native&quot;<br>        &quot;tcp:4713&quot;                         # IPv4 and IPv6 on all addresses<br>    ]<br>}<br><br># client/stream specific properties<br>pulse.rules = [<br>]</pre><p>Since we only modified PipeWire PulseAudio configuration, restarting <strong>pipewire-pulse</strong> is sufficient here.</p><pre>$ systemctl --user restart pipewire-pulse.service<br>$ systemctl --user status pipewire-pulse.service <br>● pipewire-pulse.service - PipeWire PulseAudio<br>     Loaded: loaded (/usr/lib/systemd/user/pipewire-pulse.service; enabled; preset: enabled)<br>     Active: active (running) since Tue 2025-12-30 03:12:00 CET; 40min ago<br> Invocation: 6342cff7ad284f69a96be05adc4f1a13<br>TriggeredBy: ● pipewire-pulse.socket<br>   Main PID: 554 (pipewire-pulse)<br>      Tasks: 3 (limit: 379)<br>        CPU: 5.335s<br>     CGroup: /user.slice/user-1000.slice/user@1000.service/session.slice/pipewire-pulse.service<br>             └─554 /usr/bin/pipewire-pulse<br><br>déc. 30 03:12:00 rasponkyold systemd[519]: Started pipewire-pulse.service - PipeWire PulseAudio.<br>déc. 30 03:12:09 rasponkyold pipewire-pulse[554]: mod.zeroconf-publish: error id:24 seq:76 res:-2 (Aucun fichier ou dossier de ce nom): enum params id:15 (Spa:Enum:P&gt;<br>déc. 30 03:12:09 rasponkyold pipewire-pulse[554]: mod.zeroconf-publish: error id:24 seq:78 res:-2 (Aucun fichier ou dossier de ce nom): enum params id:17 (Spa:Enum:P&gt;<br>déc. 30 03:13:02 rasponkyold pipewire-pulse[554]: mod.zeroconf-publish: error id:44 seq:189 res:-2 (Aucun fichier ou dossier de ce nom): enum params id:7 (Spa:Enum:P</pre><p>The mod.zeroconf-publish error you can see might happen at startup, but it does not prevent the module from being loaded eventually</p><pre>$ pactl list modules | grep -A7  zeroconf<br> Name: module-zeroconf-publish<br> Argument: <br> Usage counter: n/a<br> Properties:<br>  module.author = &quot;Sanchayan Maity &lt;sanchayan@asymptotic.io&quot;<br>  module.description = &quot;mDNS/DNS-SD Service Publish&quot;<br>  module.version = &quot;1.4.2&quot;</pre><p>Can we check on the pi if our remote clients managed to connect ? Sure, <strong>wpctl</strong> is a cli interface for <strong>WirePlumber</strong>:</p><pre>$ wpctl status<br>PipeWire &#39;pipewire-0&#39; [1.4.2, pi@rasponkyold, cookie:554372954]<br> └─ Clients:<br>        34. pipewire                            [1.4.2, pi@rasponkyold, pid:784]<br>        35. pipewire                            [1.4.2, pi@rasponkyold, pid:784]<br>        38. pipewire                            [1.4.2, pi@rasponkyold, pid:780]<br>        43. WirePlumber [export]                [1.4.2, pi@rasponkyold, pid:972]<br>        65. WirePlumber                         [1.4.2, pi@rasponkyold, pid:972]<br>        67. wpctl                               [1.4.2, pi@rasponkyold, pid:2325]<br>        83. PipeWire                            [1.4.2, bobby@bobby-desktop, pid:3297]<br><br>Audio<br> ├─ Devices:<br> │      95. Audio interne                       [alsa]<br> │      96. Audio interne                       [alsa]<br> │  <br> ├─ Sinks:<br> │  *   94. Audio interne Stéréo              [vol: 1.00]<br> │  <br> ├─ Sources:<br> │  <br> ├─ Filters:<br> │  <br> └─ Streams:<br>        41. PipeWire                                                    <br>             97. output_FL       &gt; bcm2835 Headphones:playback_FL [init]<br>             99. output_FR       &gt; bcm2835 Headphones:playback_FR [init]<br><br>Video<br>...<br><br>Settings<br> └─ Default Configured Devices:<br>         0. Audio/Sink    alsa_output.platform-2000b840.mailbox.stereo-fallback<br><br># Inspect the Pipewire stream<br>$ wpctl inspect 41<br>id 41, type PipeWire:Interface:Node<br>    adapt.follower.spa-node = &quot;&quot;<br>    application.id = &quot;org.pipewire.PipeWire&quot;<br>    application.language = &quot;fr_FR.UTF-8&quot;<br>  * application.name = &quot;PipeWire&quot;<br>    application.process.binary = &quot;pipewire&quot;<br>    application.process.host = &quot;bobby-desktop&quot;     &lt;---<br>    application.process.id = &quot;3297&quot;<br>    application.process.machine-id = &quot;10b867c6818e48b2a437a18888026b69&quot;<br>    application.process.user = &quot;bobby&quot;<br>    application.version = &quot;1.4.9&quot;<br>  * client.api = &quot;pipewire-pulse&quot;                  &lt;---<br>  * client.id = &quot;83&quot;<br>    clock.quantum-limit = &quot;8192&quot;<br>  * factory.id = &quot;7&quot;<br>    library.name = &quot;audioconvert/libspa-audioconvert&quot;<br>  * media.class = &quot;Stream/Output/Audio&quot;<br>    media.name = &quot;Tunnel for bobby@bobby-desktop&quot;  &lt;---<br>    node.autoconnect = &quot;true&quot;<br>    node.dont-reconnect = &quot;true&quot;<br>    node.latency = &quot;1200/48000&quot;<br>    node.loop.name = &quot;data-loop.0&quot;<br>  * node.name = &quot;PipeWire&quot;<br>    node.rate = &quot;1/48000&quot;<br>    node.want-driver = &quot;true&quot;<br>    object.register = &quot;false&quot;<br>  * object.serial = &quot;210&quot;<br>    pipewire.client.access = &quot;restricted&quot;<br>    port.group = &quot;stream.0&quot;<br>    pulse.attr.maxlength = &quot;4194304&quot;<br>    pulse.attr.minreq = &quot;4800&quot;<br>    pulse.attr.prebuf = &quot;9604&quot;<br>    pulse.attr.tlength = &quot;14400&quot;<br>    pulse.corked = &quot;true&quot;<br>    pulse.server.type = &quot;tcp&quot;                     &lt;--- <br>    stream.is-live = &quot;true&quot;<br>    target.object = &quot;alsa_output.platform-2000b840.mailbox.stereo-fallback&quot;<br>    window.x11.display = &quot;:0&quot;</pre><p>Looks like the desktop reconnected seemlessly on the new pipewire-pulse sink, great !</p><h4>2. Check server is available on clients</h4><p>Let’s see on the desktop if it really works: First, we can see our new PipeWire sink is now exposed on zeroconf</p><pre>$ avahi-browse -a |grep pulse<br>...<br>+ enp2s0 IPv4 pi@rasponkyold: Audio interne St__r__o        _pulse-sink._tcp     local<br>...<br><br>$ pactl list sinks<br>...<br>Destination #203<br> État : SUSPENDED<br> Nom : tunnel.rasponkyold.local.alsa_output.platform-2000b840.mailbox.stereo-fallback<br> Description : Audio interne Stéréo on pi@rasponkyold<br> Pilote : PipeWire<br> Spécification de l’échantillon : s16le 2ch 48000Hz<br> Plan des canaux : front-left,front-right<br> Module du propriétaire : 4294967295<br> Sourdine : non<br> Volume : front-left: 65536 / 100% / 0,00 dB,   front-right: 65536 / 100% / 0,00 dB<br>         balance 0,00<br> Volume de base : 65536 / 100% / 0,00 dB<br> Source du moniteur : tunnel.rasponkyold.local.alsa_output.platform-2000b840.mailbox.stereo-fallback.monitor<br> Latence : 0 usec, configuré 0 usec<br> Marqueurs : NETWORK DECIBEL_VOLUME LATENCY <br> Propriétés :<br>  audio.format = &quot;S16LE&quot;<br>  audio.rate = &quot;48000&quot;<br>  audio.channels = &quot;2&quot;<br>  audio.position = &quot;[FL,FR]&quot;<br>  node.name = &quot;tunnel.rasponkyold.local.alsa_output.platform-2000b840.mailbox.stereo-fallback&quot;<br>  device.description = &quot;Audio interne Stéréo on pi@rasponkyold&quot;<br>  node.virtual = &quot;true&quot;<br>  node.network = &quot;true&quot;<br>  media.class = &quot;Audio/Sink&quot;<br>  media.name = &quot;pulse&quot;<br>  stream.is-live = &quot;true&quot;<br>  node.want-driver = &quot;true&quot;<br>  node.autoconnect = &quot;true&quot;<br>  port.group = &quot;stream.0&quot;<br>  adapt.follower.spa-node = &quot;&quot;<br>  object.register = &quot;false&quot;<br>  factory.id = &quot;7&quot;<br>  clock.quantum-limit = &quot;8192&quot;<br>  node.loop.name = &quot;data-loop.0&quot;<br>  library.name = &quot;audioconvert/libspa-audioconvert&quot;<br>  client.id = &quot;91&quot;<br>  object.id = &quot;75&quot;<br>  object.serial = &quot;203&quot;<br> Formats :<br>  pcm</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/399/1*PoFFw2IYrvEEXfAMERbYDg.png" /><figcaption>Our new PipeWire-pulse in Gnome Audio quick settings</figcaption></figure><h4>3. Bluetooth</h4><p>Good news ! Everything from <a href="https://medium.com/@mathieu-requillart/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-bluetooth-64c347ee0d22">my Bluetooth article from 2020</a> still applies and works with <strong>pipewire-pulse</strong> ! Of course, skip the part where you install pulseaudio. Existing PIN-based connections continue to work, on Android at least. <br>Now <strong>WirePlumber</strong> automatically manages routing bluetooth audio streams within <strong>PipeWire</strong>, as we can see below:</p><pre>$ wpctl status<br>PipeWire &#39;pipewire-0&#39; [1.4.2, pi@rasponkyold, cookie:*********]<br> └─ Clients:<br>        40. pipewire                            [1.4.2, pi@rasponkyold, pid:573]<br>        73. WirePlumber [export]                [1.4.2, pi@rasponkyold, pid:2094]<br>        81. Snapcast                            [1.4.2, pi@rasponkyold, pid:1993]<br>        89. WirePlumber                         [1.4.2, pi@rasponkyold, pid:2094]<br>        95. pipewire                            [1.4.2, pi@rasponkyold, pid:2188]<br>        97. pipewire                            [1.4.2, pi@rasponkyold, pid:2188]<br>       110. wpctl                               [1.4.2, pi@rasponkyold, pid:2198]<br><br>Audio<br> ├─ Devices:<br> │      63. Audio interne                       [alsa]<br> │      90. Audio interne                       [alsa]<br> │     102. Pixel 6a                            [bluez5]<br> │  <br> ├─ Sinks:<br> │  *   118. Audio interne Stéréo              [vol: 1.00]<br> │  <br> ├─ Sources:<br> │  <br> ├─ Filters:<br> │  <br> └─ Streams:<br>        45. bluez_input.C8_2A_DD_A7_D5_0D.2                             <br>             48. output_FR       &gt; bcm2835 Headphones:playback_FR [active]<br>            104. output_FL       &gt; bcm2835 Headphones:playback_FL [active]<br>        ...<br><br>Video<br>...<br><br>Settings<br> └─ Default Configured Devices:<br>         0. Audio/Sink    alsa_output.platform-2000b840.mailbox.stereo-fallback</pre><p>This shows the phone connected as a bluez5 device, with WirePlumber<br>automatically routing the Bluetooth audio stream to the default ALSA sink.</p><h4>Conclusion</h4><p>Replacing a PulseAudio TCP server with PipeWire turned out to be much easier than expected.<br>Thanks to pipewire-pulse, all existing clients keep working — TCP reconnections, Bluetooth audio, MPD, and others. No app or client needs any modification.</p><p>From the outside, <em>nothing really changes</em>, which is perfect when swapping out a core component like the audio server.<br>Under the hood, though, you now have a modern, flexible PipeWire stack that provides lower latency, better device management, and full PulseAudio compatibility.</p><p>This new base will help push the Raspberry Pi multiroom system even further — and that’s exactly what the next article will explore.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b6016d9360c5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry pi audio server I wanted — Multiroom support with Snapcast]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-multiroom-support-with-snapcast-30e95fb74eb4?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/30e95fb74eb4</guid>
            <category><![CDATA[debian]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[multiroom]]></category>
            <category><![CDATA[snapcast]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Wed, 10 Dec 2025 14:40:17 GMT</pubDate>
            <atom:updated>2026-03-06T15:56:50.946Z</atom:updated>
            <content:encoded><![CDATA[<h3>My ultimate guide to the Raspberry pi audio server I wanted — Multiroom support with Snapcast</h3><h3><strong>Part 9 — MultiRoom (Snapcast)</strong></h3><p><strong>Snapcast</strong> is one of the most reliable and elegant ways to build a perfectly synchronized multi-room audio system. Snapcast guarantees <strong>sample-accurate playback</strong>, scales to many rooms, and is extremely lightweight on the client side, making it ideal for <strong>old Raspberry Pis (Pi B+, Pi Zero, Pi 2).</strong></p><p>This guide walks you through <strong>how to </strong>make Snapcast fits into a NAS + Raspberry Pi architecture, and <strong>how</strong> to set it up ith <strong>MPD</strong>, <strong>Spotify</strong>, <strong>AirPlay</strong>, and <strong>PipeWire</strong>, with all differences between <strong>Debian 12 (Bookworm) </strong>and recently published <strong>Debian 13 (Trixie)</strong>.</p><h3>1. Why Snapcast?</h3><p>I initially tried to do everything directly on the Raspberry Pi with <strong>MPD</strong>, <strong>Spotifyd</strong> and <strong>PipeWire</strong>, but I kept getting random audio cracks — especially every time <strong>Spotifyd</strong> loaded the next track. Old ARMv6 boards just can’t seem to keep up with modern audio stacks.</p><p>That’s the moment I decided to stop fighting the hardware and move the heavy work to my NAS, keeping the Raspberry Pi B+ as an ultra-light Snapclient endpoints.</p><p>Snapcast solves all of these at once:</p><p>Here are main <strong>Snapcast</strong> main components :</p><ul><li><strong>Snapclient (Raspberry Pi):</strong> Plays audio from <strong>Snapserver</strong> with perfect sync</li><li><strong>Snapserver (NAS)</strong>: Mixes and distributes all audio streams from various sources: MPD, Spotify, AirPlay, PipeWire, etc</li></ul><h3>2. Installing Snapserver on Debian 12 &amp; 13</h3><h4>Debian 12 (Bookworm)</h4><p>Debian 12 ships <strong>Snapcast 0.30 </strong>in<strong> </strong>backports, or 0.26, which are too old because they lack <strong>AddStream / RemoveStream</strong> features which are mandatory for PipeWire source auto-management (7).</p><p>You need at least <strong>Snapcast 0.31</strong> from GitHub.</p><pre>$ wget https://github.com/snapcast/snapcast/releases/download/v0.31.0/snapserver_0.34.0-1_amd64_bookworm.deb<br>$ sudo dpkg -i snapserver_0.31.0-1_amd64_bookworm.deb</pre><h4>Debian 13 (Trixie)</h4><p>Debian 13 ships <strong>Snapcast 0.31 natively, </strong>just install it.</p><pre>$ sudo apt install snapserver</pre><p>We’ll also install Snapweb to control our streams:</p><pre>$ wget https://github.com/snapcast/snapweb/releases/download/v0.9.3/snapweb_0.9.3-1_all.deb<br>$ sudo dpkg -i snapweb_0.9.3-1_all.deb</pre><p>Edit /etc/snapserver.conf:</p><pre>[server]<br><br>[http]<br>enabled = true<br>bind_to_address = 0.0.0.0<br>doc_root = /usr/share/snapweb<br>[tcp]<br>enabled = true<br>bind_to_address = 0.0.0.0</pre><p>This enables:</p><ul><li><strong>Snapweb UI</strong> (on port 1780)</li><li><strong>TCP stream interface</strong> (port 1704)</li></ul><h3>3. Adding MPD as a Snapcast Stream</h3><h4>Configure MPD FIFO Output</h4><p>FIFO output is the classic and most reliable way to feed MPD into Snapcast. It’s simple and low-overhead.</p><p>Edit /etc/mpd.conf:</p><pre>audio_output {<br>        type            &quot;fifo&quot;<br>        name            &quot;SnapMPD&quot;<br>        path            &quot;/tmp/mpdfifo&quot;<br>        format          &quot;48000:16:2&quot;<br>        mixer_type      &quot;software&quot;<br>}</pre><p>And restart MPD</p><pre>$ sudo systemctl restart mpd.service</pre><h4>Add it to Snapserver</h4><p>In /etc/snapserver.conf under [stream], add:</p><pre>[stream]<br># ...<br>source = pipe:///tmp/mpdfifo?name=MPD&amp;devicename=SnapMPD</pre><p>And restart snapserver:</p><pre>$ sudo systemctl restart snapserver</pre><p>Done — your NAS can now stream its MPD audio to all rooms.</p><p>For now, I don’t have any way to stream the Rasperry Pi’s MPD (handling audio CDs and USB sticks) to stream to snapcast, but that may be possible in the future.</p><h3>4. Adding Spotify (via Librespot)</h3><h4>Install Librespot from source</h4><p>Debian repositories are too old, so we need to install <strong>Rust</strong> and <strong>Librespot</strong> manually:</p><pre>$ apt-get install build-essential libasound2-dev<br>$ curl https://sh.rustup.rs -sSf | sh<br>$ cargo install librespot<br>$ sudo mv .cargo/bin/librespot /usr/local/bin</pre><p>This gives you the latest Spotify Connect implementation.</p><h4>Snapserver Stream</h4><p>In /etc/snapserver.conf under [stream], add:</p><pre>[stream]<br># ...<br>source = librespot:///usr/local/bin/librespot?name=Spotify&amp;devicename=SnapSpot&amp;bitrate=320&amp;volume=100&amp;normalize=false</pre><p>And restart snapserver:</p><pre>$ sudo systemctl restart snapserver</pre><p>Now Snapserver exposes a Spotify Connect endpoint in Snapweb.</p><h3>5. Adding AirPlay (shairport-sync)</h3><h4>Install shairport-sync</h4><p>Debian 12 → v3.3.8 (AirPlay 1 only)<br>Debian 13 → v4.3.7 (AirPlay 2 supported !)</p><pre>$ sudo apt install shairport-sync<br>$ sudo systemctl disable --now shairport-sync</pre><p>Shairport will be started by Snapserver, by <em>_snapserver</em> user, so we have to disable the service startup and edit a few <strong>dbus</strong> policies and add our <em>_snapserver</em> user in the owners of shairport dbus services,<a href="https://mathieu-requillart.medium.com/set-up-a-b83d9c980e75"> as we did on the raspberry in our previous article about AirPlay</a>.</p><p>Place the following directives in the <em>&lt;busconfig&gt;</em> section</p><p>Edit <em>shairport-sync-dbus-policy.conf</em>:</p><p><strong>Debian 12</strong>: /etc/dbus-1/system.d/shairport-sync-dbus-policy.conf<br><strong>Debian 13</strong>: /usr/share/dbus-1/system.d/shairport-sync-dbus-policy.conf<br>Add:</p><pre>&lt;policy user=&quot;_snapserver&quot;&gt;<br>    &lt;allow own=&quot;org.gnome.ShairportSync&quot;/&gt;<br>&lt;/policy&gt;</pre><p>Edit <em>shairport-sync-mpris-policy.conf</em>:</p><p><strong>Debian 12</strong>: /etc/dbus-1/system.d/shairport-sync-mpris-policy.conf<br><strong>Debian 13</strong>: /usr/share/dbus-1/system.d/shairport-sync-mpris-policy.conf</p><pre>&lt;policy user=&quot;_snapserver&quot;&gt;<br>    &lt;allow own=&quot;org.mpris.MediaPlayer2.ShairportSync&quot;/&gt;<br>&lt;/policy&gt;</pre><p>Restart Dbus:</p><pre>$ sudo systemctl restart dbus.service</pre><h4>Snapserver Stream</h4><p>In /etc/snapserver.conf under [stream], add:</p><pre>[stream]<br># ...<br>source = airplay:///usr/bin/shairport-sync?name=Airplay&amp;devicename=SnapAir&amp;port=5000</pre><p>And restart snapserver:</p><pre>$ sudo systemctl restart snapserver</pre><p>You now have a fully integrated AirPlay endpoint (1 or 2 depending on Debian version).</p><h3>6. Installing Snapclient on Raspberry Pi</h3><p>Snapclient doesn’t really benefit from version 0.31 — only Snapserver does. On Debian 12, both Snapclient 0.26 (bookworm) and 0.30 (bookworm-backports) work perfectly fine.</p><p>The only real requirement is Snapserver ≥ 0.31 because it restores AddStream and RemoveStream.</p><p>Of course, for a multi room setup and the full Snapcast experience, you have to repeat this part on every raspberry behind speakers that you want.</p><pre>$ sudo apt install snapclient</pre><h4>Use Pulse/PipeWire as backend</h4><p>Edit /etc/default/snapclient:</p><pre>SNAPCLIENT_OPTS=&quot;--player pulse&quot;</pre><h4>Run Snapclient as a user service</h4><p>Running Snapclient as a user service ensures PulseAudio/PipeWire is already running before Snapclient starts. The system service starts too early and can fail silently.</p><pre>$ sudo systemctl disable --now snapclient.service<br>$ systemctl --user enable --now snapclient.service</pre><h3>7. PipeWire Autodiscovery</h3><p>PipeWire can automatically detect Snapcast servers and redirect audio streams to them. With this module, any audio played on this machine (browser, apps, system sounds, games, anything) automatically appears as a Snapcast stream on your network. Zero configuration. Very useful for PCs, laptops, HTPCs, etc.</p><p>Create:</p><p>~/.config/pipewire/pipewire.conf.d/my-snapcast-discover.conf</p><pre>context.modules = [<br>{   name = libpipewire-module-snapcast-discover<br>    args = {<br>        stream.rules = [<br>            {   matches = [<br>                    { snapcast.ip = &quot;~.*&quot; }<br>                ]<br>                actions = {<br>                    create-stream = {<br>                      node.name = &quot;Snapcast&quot;<br>                      node.description = &quot;Snapcast Server&quot;<br>                    }<br>                }<br>            }<br>        ]<br>    }<br>}<br>]</pre><p>And restart <strong>Pipewire</strong></p><pre>$ systemctl --user restart pipewire-pulse.service</pre><p>The Snapserver then appears as “Snapcast Server” in your audio outputs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/406/1*6qT10k2HeEPqTyCxUs16Lw.png" /></figure><h3>8. Conclusion</h3><p>After setting up Snapcast with MPD, Spotify (Librespot), AirPlay, and PipeWire, the results are exactly what I wanted. Old Raspberry Pis like the Pi B+ become stable, low-CPU multiroom endpoints. Audio is perfectly synchronized across rooms, without the cracks and stuttering I had when trying to run everything directly on the Pi.</p><p>Snapserver centralizes all sources on the NAS, and Snapclient handles playback reliably.</p><p>Management is straightforward: the <strong>web interface </strong>on<strong> <em>http://&lt;nas&gt;:1780</em></strong> provides full control over streams and clients, and the <a href="https://f-droid.org/packages/de.badaix.snapcast/"><strong>Snapcast Android app</strong></a> allows easy control from anywhere in the house. You get a list of all Snapclient on your network and can choose which source to play on it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/931/1*UfLmZs0GBBMF_umwOXat8w.png" /><figcaption>Snapweb on http://&lt;nas&gt;:1780</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*16TiykB8V5UwC0n9VrDncA.png" /><figcaption>Snapcast Android application</figcaption></figure><p>Combined with PipeWire Snapcast discovery, any desktop or laptop application can become a stream without manual configuration.</p><p>Overall, this setup proves that you don’t need new hardware for true multiroom audio. With a NAS as the central hub and lightweight Raspberry Pi clients, even old ARM boards can deliver a rock-solid, fully controllable, multi-source audio system.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=30e95fb74eb4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Switching Pulseaudio TCP Client to Pipewire]]></title>
            <link>https://mathieu-requillart.medium.com/switching-pulseaudio-tcp-client-to-pipewire-7b7411a94c72?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/7b7411a94c72</guid>
            <category><![CDATA[debian-11]]></category>
            <category><![CDATA[pulseaudio]]></category>
            <category><![CDATA[pipewire]]></category>
            <category><![CDATA[fedora]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Mon, 12 Jun 2023 22:10:21 GMT</pubDate>
            <atom:updated>2025-12-10T18:52:20.561Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://pipewire.org/"><strong>Pipewire</strong></a> has gained significant popularity in the Linux media world for its remarkable audio and display management capabilities, particularly on <a href="https://wayland.freedesktop.org/"><strong>Wayland</strong></a>. Fedora embraced Pipewire as the default sound management system starting <a href="https://fedoraproject.org/wiki/Changes/DefaultPipeWire">since <strong>Fedora 34</strong></a>. However, during its early stages, when I needed TCP and zeroconf support for my Raspberry Pi, Pipewire lacked these features, making it unsuitable for my needs at the time.</p><p>Initially, I stuck with Pulseaudio, but as time went on, I encountered more issues with the TCP client, particularly when using streaming services like YouTube. When Fedora 36 introduced Pipewire 0.3.4x with TCP and zeroconf support, I gave it another try.</p><pre>$ sudo dnf install --allowerasing  pipewire-pulse</pre><p>Add a few lines in <em>/etc/pipewire/pipewire-pulse.conf </em>to allow your pipewire client to discover the PulseAudio and Airplay Server on the Pi</p><pre>context.modules = [<br># …<br>{ name = libpipewire-module-protocol-native }<br>{ name = libpipewire-module-zeroconf-discover<br> args = { }<br>}<br>{ name = libpipewire-module-protocol-pulse<br> args = { }<br>}]<br>context.exec = [<br># …<br> { path = &quot;pactl&quot; args = &quot;load-module module-raop-discover&quot; }<br>]</pre><p>Then reboot your system to allow <strong>Pipewire</strong> to replace <strong>Pulseaudio</strong> as the sound manager.</p><p>You can also use systemd to achieve the same result.</p><pre>$ systemctl --user disable --now pulseaudio.service pulseaudio.socket<br>$ systemctl --user enable --now pipewire-pulse.service pipewire-pulse.socket</pre><p>And waow the difference is really amazing. Switching output is really flawless, the audio starts immediately without any glitches. <strong>Pipewire</strong> proved to be a significant improvement over <strong>Pulseaudio</strong>, even though I was relatively content with <strong>Pulseaudio</strong>’s performance. I had some<a href="https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2548"> bad surprises</a> with some Pipewire updates but I’m really happy about it now.</p><p>As <strong>Pipewire</strong> keeps <strong>Pulseaudio</strong> compatibity, the gnome setting to change the audio output stays the same as before.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/986/1*zCBgGti5Zvr2gumsDWgYjQ.png" /><figcaption>Gnome sound settings</figcaption></figure><p><a href="https://extensions.gnome.org/extension/5135/audio-selector/">This extension</a> can also be noted as it helps changing directly from the activity bar and functions exceptionally well compared to its previous state.</p><p>Gnome 43 supports this menu natively, so the extension became useless.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/250/1*rGQpuV-vVwi3EcCSurKSvw.png" /><figcaption>Gnome extension Audio-Selector</figcaption></figure><p>I’ve also successfully transitioned my HTPC server to <strong>Debian</strong> 11 and wanted to implement the same <strong>Pipewire</strong> setup. Obviously the procedure is a bit different, and well documented on Debian <a href="https://wiki.debian.org/PipeWire">Wiki</a>. As the wiki states this method isn’t offically supported, but works well enough nonetheless.<br>We need to add backports repositories if it isn’t done already</p><p><em>/etc/apt/sources.list.d/backports.list</em></p><pre>deb http://deb.debian.org/debian bullseye-backports main</pre><p>First we’ll install pipewire from the backports</p><pre>$ sudo apt update<br>$ sudo apt install pipewire-pulse/bullseye-backports pipewire/bullseye-backports pipewire-bin/bullseye-backports libpipewire-0.3-modules/bullseye-backports wireplumber/bullseye-backports</pre><p>Now we can configure and run Pipewire instead of Pulseaudio.</p><p>First copy <strong>systemd</strong> unit files from doc</p><pre>$ sudo cp /usr/share/doc/pipewire/examples/systemd/user/pipewire-pulse.* /etc/systemd/user/</pre><p>Then copy <strong>Pipewire</strong> configuration file from doc to /etc/pipewire/pipewire-pulse.conf file and make the same configuration changes as described earlier for Fedora.</p><pre>$ sudo apt install libpipewire-module-raop-discover #Install Airplay Discover Module<br>$ sudo cp /usr/share/pipewire/pipewire-pulse.conf /etc/pipewire/pipewire-pulse.conf # copy the pipewire-pulse configuration file from doc</pre><p>Reload the systemd daemon and disable <strong>Pulseaudio</strong> while enabling <strong>Pipewire</strong>:</p><pre>$ systemctl --user daemon-reload<br>$ systemctl --user disable --now pulseaudio.service pulseaudio.socket<br>$ systemctl --user enable --now pipewire pipewire-pulse</pre><p>You can reboot to make sure everything runs fine at boot</p><p>I have quite a lot of error when <strong>Pipewire</strong> starts, but everything seems to be running fine. I tried to follow <a href="https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning#for-debianubuntu-based-distributions">pipewire wiki for the rt errors</a> without success</p><pre>$ systemctl --user status pipewire-pulse<br>sept. 23 21:30:08 htpc pipewire-pulse[885]: mod.rt: Can&#39;t find xdg-portal: (null)<br>sept. 23 21:30:08 htpc pipewire-pulse[885]: mod.rt: found session bus but no portal<br>sept. 23 21:30:08 htpc pipewire-pulse[885]: mod.rt: RTKit error: org.freedesktop.DBus.Error.AccessDenied<br>sept. 23 21:30:08 htpc pipewire-pulse[885]: mod.rt: could not make thread 903 realtime using RTKit: Permission non accordée<br>sept. 23 21:30:08 htpc pipewire-pulse[902]: 536870912<br>sept. 23 21:30:11 htpc pipewire-pulse[885]: mod.pulse-tunnel: failed to connect: Accès refusé<br>sept. 23 21:30:11 htpc pipewire-pulse[885]: mod.zeroconf-discover: Can&#39;t load module: Opération non permise<br>sept. 23 21:30:13 htpc pipewire-pulse[885]: mod.pulse-tunnel: underflow</pre><p>Especially, the <em>mod.zeroconf-discover: Can’t load module: Opération non permise </em>is surprising because the module is working perfectly. Although the <em>zeroconf-discover </em>module is not listed with <strong>pactl list modules</strong>, my raspberry sink does appear in pipewire. If I remove the module in configuration, the error disappear but so does the sink 🙃</p><p>Those errors disappeared later when I upgraded to <strong>Debian</strong> 12 Bookworm. In this version the backports are not needed.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7b7411a94c72" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry pi audio server I wanted — Apple AirPlay]]></title>
            <link>https://mathieu-requillart.medium.com/set-up-a-b83d9c980e75?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/b83d9c980e75</guid>
            <category><![CDATA[systemd]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[shairport-sync]]></category>
            <category><![CDATA[airplay]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Sat, 30 Apr 2022 23:35:20 GMT</pubDate>
            <atom:updated>2025-12-13T01:40:59.724Z</atom:updated>
            <content:encoded><![CDATA[<h3>My ultimate guide to the Raspberry pi audio server I wanted — Apple AirPlay</h3><h3>Part 8 — AirPlay</h3><p>I also wanted to add <a href="https://www.apple.com/fr/airplay/"><strong>AirPlay</strong></a> support to the Raspberry for my friends with Apple devices. It’s also a common service on most open source and commercial audio servers. The way to go is to install <a href="https://github.com/mikebrady/shairport-sync"><strong>Shairport-sync</strong></a>. Installation is pretty easy, but configuration with <strong>PulseAudio</strong> is a bit tricky.<br>We’ll want to run <strong>Shairport-sync</strong> as a user <strong>systemd</strong> unit with our <em>pi</em> user and configure it to use our <strong>PulseAudio</strong> server as audio backend to bypass the <a href="https://github.com/mikebrady/shairport-sync">limitations announced in the documentation</a>.</p><pre>$ sudo apt install shairport-sync<br>$ systemctl --user enable shairport-sync</pre><p>This will copy default systemd unit in <em>~/.config/systemd/user/shairport-sync.service</em> (if not you’ll have to create it), but we need to make some adjustments to make it work properly:<br> - Remove the <strong>Avahi</strong> mentions and <em>User</em> and <em>Group</em> directive which can’t be used within a user <strong>sytemd</strong> unit.<br>- Change the install method to <em>default.target</em> instead of <em>multi-user</em> to boot properly with user <strong>systemd<br>- </strong>Change restart policy, for some reason <strong>Shairport-sync</strong> seems to start quicker than <strong>PulseAudio</strong> and fail to connect to it and start properly at boot.</p><p><em>~/.config/systemd/user/shairport-sync.service</em></p><pre>[Unit]<br>Description=Shairport Sync - AirPlay Audio Receiver<br>Wants=sound.target<br>After=sound.target<br>Wants=network-online.target<br>After=network.target network-online.target<br><br>[Service]<br>Type=simple<br>EnvironmentFile=-/etc/default/shairport-sync<br>ExecStart=/usr/bin/shairport-sync $DAEMON_ARGS <br>Restart=always<br>RestartSec=5<br><br>[Install]<br>WantedBy=default.target</pre><p>Since we use our user <em>pi</em> instead of default <em>shairport-sync</em> user, we also need to edit a few <strong>dbus</strong> policies and to add our <em>pi</em> user in the owners of shairport dbus services. Place the following directives in the <em>&lt;busconfig&gt;</em> section</p><p><strong>Debian 12 Bookworm</strong><em>: /etc/dbus-1/system.d/shairport-sync-dbus-policy.conf<br></em><strong>Debian 13 Trixie</strong>: <em>/usr/share/dbus-1/system.d/shairport-sync-dbus-policy.conf</em></p><pre>&lt;policy user=&quot;pi&quot;&gt;<br> &lt;allow own=&quot;org.gnome.ShairportSync&quot;/&gt;<br>&lt;/policy&gt;</pre><p><strong>Debian 12 Bookworm</strong><em>: /etc/dbus-1/system.d/shairport-sync-mpris-policy.conf<br></em><strong>Debian 13 Trixie</strong> : <em>/usr/share/dbus-1/system.d/shairport-sync-mpris-policy.conf</em></p><pre>&lt;policy user=&quot;pi&quot;&gt;<br> &lt;allow own=&quot;org.mpris.MediaPlayer2.ShairportSync&quot;/&gt;<br>&lt;/policy&gt;</pre><pre>$ sudo systemctl restart dbus</pre><p>Finally, as we’re used in this seup, we configure <strong>shairport-sync</strong> to use <strong>PulseAudio</strong> backend instead of <strong>Alsa</strong></p><p><em>/etc/shairport-sync.conf</em></p><pre>general = {<br> name = &quot;%H&quot;; // This means &quot;Hostname&quot; - see below. This is the name the service will advertise to iTunes.<br> output_backend = &quot;pa&quot;; // Run &quot;shairport-sync -h&quot; to get a list of all output_backends, e.g. &quot;alsa&quot;, &quot;pipe&quot;, &quot;stdout&quot;. The default is the first one.<br> mdns_backend = &quot;avahi&quot;; // Run &quot;shairport-sync -h&quot; to get a list of all mdns_backends. The default is the first one.<br>};<br>pa = {<br> server = &quot;127.0.0.1&quot;; // Set this to override the default pulseaudio server that should be used.<br> application_name = &quot;Shairport Sync&quot;; //Set this to the name that should appear in the Sounds &quot;Applications&quot; tab when Shairport Sync is active.<br>};</pre><p>We can finally start <strong>shairport-sync</strong>…</p><pre>$ systemctl --user start shairport-sync.service</pre><p>And see it running and connected to <strong>PulseAudio</strong></p><pre>$ journalctl --user -u shairport-sync.service<br>Apr 18 01:05:53 rasponkyo systemd[688]: shairport-sync.service: Consumed 1.607s CPU time.<br>Apr 18 01:05:53 rasponkyo systemd[688]: Started Shairport Sync - AirPlay Audio Receiver.<br>$ pactl list clients<br>Client #20<br> Driver: protocol-native.c<br> Owner Module: 11<br> Properties:<br> application.name = &quot;Shairport Sync&quot;<br> native-protocol.peer = &quot;TCP/IP client from 127.0.0.1:50616&quot;<br> native-protocol.version = &quot;34&quot;<br> application.process.id = &quot;2376&quot;<br> application.process.user = &quot;pi&quot;<br> application.process.host = &quot;rasponkyo&quot;<br> application.process.binary = &quot;shairport-sync&quot;<br> application.language = &quot;C&quot;<br> application.process.machine_id = &quot;94407542ae804e6f9e791df75037e3d2&quot;</pre><h3>Using our new AirPlay server as a PulseAudio sink</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/772/1*lS7yGk35TsVMA1Qt7Fz8GA.png" /></figure><p>Since version 11 <strong>PulseAudio</strong> natively supports <strong>AirPlay</strong> servers as sink, so we can use it as a remote sink as <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-pulseaudio-tcp-cbaa5cb966e6">we did with PulseAudio TCP</a>.</p><p>Then you can go in your sound settings and set the output to your new <strong>AirPlay</strong> server, running on port 5000.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/975/1*JPQrt_UMZLAgCQ67sjTj7Q.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b83d9c980e75" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry pi audio server I wanted — Perfomance improvements]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-perfomance-improvements-a087ab4e25ab?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/a087ab4e25ab</guid>
            <category><![CDATA[pulseaudio]]></category>
            <category><![CDATA[mpd]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[audio]]></category>
            <category><![CDATA[spotify]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Fri, 30 Apr 2021 10:25:14 GMT</pubDate>
            <atom:updated>2021-04-30T11:43:20.379Z</atom:updated>
            <content:encoded><![CDATA[<h3><a href="https://medium.com/p/cbaa5cb966e6/edit?source=your_stories_page---------------------------">My ultimate guide to the Raspberry pi audio server I wanted</a> — Perfomance improvements</h3><p>This schema below is the setup exactly as explained in this guide, but it’s not the one I use today. I had to make a few changes to the initial setup I had in mind because of performances issues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/819/1*Os4ECbXDF-wz-Yo4rhRdQw.png" /></figure><p>I had audio cracks and lags with Spotify Connect, <strong>MPD</strong>, and DLNA of course since <strong>UpMPDcli</strong> uses <strong>MPD</strong> as backend, especially at the beginning or end of songs. So I monitored the Pi with <a href="https://htop.dev/"><strong>htop</strong></a> as I was using it and noticed the pi had a high load average, above 1, because the CPU often reached 100% usage, which meant the Pi wasn’t fast enough to perform all calculations in time to render audio, which is something you really don’t want when dealing with Hi-Fi.</p><p>I noticed <strong>Pulsaudio</strong> and <strong>LibreSpot</strong> used a lot of CPU while running, around 40% each. But there wasn’t much I could do about it at that time (160kbps with Spotify is poor quality enough). I tried to increasing Pulseaudio and MPD <a href="https://www.tecmint.com/set-linux-process-priority-using-nice-and-renice-commands/">priority in the kernel</a>, which improved things without solving my problem completely. <a href="https://www.tomshardware.com/how-to/overclock-any-raspberry-pi">Overclocking the Pi</a> did not help either.</p><p>But I also noticed something I could act on : the main <strong>MPD</strong> instance, unlike the USB one, maintained a CPU usage around 15% when it was supposedly idle and even higher in use, so I concluded it was the satelitte connexion with the NAS <strong>MPD</strong> which was causing this overload. So I dumped the <strong>MPD</strong> satellite instance on the Pi and <strong>MPD</strong> CPU usage dropped significantly, especially when idle : 0%.</p><p>I already had the <strong>MPD</strong> database on my NAS since the satellite mode of the Pi relied on its database so I set the Raspberry <strong>PulseAudio</strong> sink as <strong>MPD</strong> audio output on the NAS. Then I just had to replace the Pi address IP by the NAS one in <strong>M.A.L.P.</strong> and <strong>ympd</strong> to be able to browse music from <strong>MPD</strong> on the NAS and play it on the Pi.</p><p>Since then, <strong>LibreSpot</strong> had dropped to 30% and <strong>Pulseaudio</strong> 10%. I’m not sure it was <strong>Pulseaudio</strong> and <strong>LibreSpot </strong>updates<strong> </strong>which caused this or a DAC driver update, or maybe both, but I’m really happy about it because I could improve Spotify quality to 320kpbs thanks to it.</p><p>It was really a win-win since I improved the performance of the Pi, lags and cracks disappeared, without losing any feature in my setup, and the Pi became completely independant from the NAS which makes more sense in my opinion.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a087ab4e25ab" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My ultimate guide to the Raspberry pi audio server I wanted — Spotify]]></title>
            <link>https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-spotify-ce549656af06?source=rss-1bed43070a36------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce549656af06</guid>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[spotify]]></category>
            <category><![CDATA[pulseaudio]]></category>
            <dc:creator><![CDATA[Mathieu Réquillart]]></dc:creator>
            <pubDate>Sun, 08 Nov 2020 17:53:46 GMT</pubDate>
            <atom:updated>2025-12-10T18:58:31.649Z</atom:updated>
            <content:encoded><![CDATA[<h3><a href="https://medium.com/p/a087ab4e25ab/edit?source=your_stories_page-------------------------------------">My ultimate guide to the Raspberry pi audio server I wanted — S</a>potify</h3><h3>Part 7 — Spotify (Raspotify →Spotifyd)</h3><p>A much appreciated feature in recent connected amplifiers is the ability to play music from streaming services. Even if I have a huge media library, everything is not in it, so I wanted to add support of Spotify on my HiFi server.</p><p>The obvious choice was <a href="https://github.com/dtcooper/raspotify"><strong>Raspotify</strong></a>, a Raspberry Spotify client based on <a href="https://github.com/librespot-org/librespot"><strong>librespot</strong></a>, an open source Spotify client. However, a premium Spotify account is required.</p><p>The installation is very easy :</p><pre>$ curl -sL https://dtcooper.github.io/raspotify/install.sh | sh</pre><p>The configuration file is located at <em>/etc/default/raspotify</em></p><pre># /etc/default/raspotify --Arguments/configuration for librespot<br># Device name on Spotify Connect<br>DEVICE_NAME=&quot;onkyo-raspotify&quot;<br># Bitrate, one of 96 (low quality), 160 (default quality), or 320 (high quality)<br>BITRATE=&quot;160&quot;<br># Additional command line arguments for librespot can be set below.<br># See `librespot -h` for more info. Make sure whatever arguments you specify<br># aren&#39;t already covered by other variables in this file. (See the daemon&#39;s<br># config at `/lib/systemd/system/raspotify.service` for more technical details.)<br>#<br># To make your device visible on Spotify Connect across the Internet add your<br># username and password which can be set via &quot;Set device password&quot;, on your<br># account settings, use ` --username` and ` --password`.<br>#<br># To choose a different output device (ie a USB audio dongle or HDMI audio out),<br># use ` --device` with something like ` --device hw:0,1`. Your mileage may vary.<br>#<br>#OPTIONS=&quot; --username &lt;USERNAME&gt; --password &lt;PASSWORD&gt;&quot;<br># Uncomment to use a cache for downloaded audio files. Cache is disabled by<br># default. It&#39;s best to leave this as-is if you want to use it, since<br># permissions are properly set on the directory `/var/cache/raspotify&#39;.<br>#CACHE_ARGS=&quot; --cache /var/cache/raspotify&quot;<br># By default, the volume normalization is enabled, add alternative volume<br># arguments here if you&#39;d like, but these should be fine.<br>VOLUME_ARGS=&quot;--linear-volume --initial-volume=100&quot;<br>#VOLUME_ARGS=&quot;--enable-volume-normalisation --linear-volume --initial-volume=100&quot;<br># Backend could be set to pipe here, but it&#39;s for very advanced use cases of<br># librespot, so you shouldn&#39;t need to change this under normal circumstances.<br>#BACKEND_ARGS=&quot;--backend alsa&quot;</pre><p>First I modified the <em>DEVICE_NAME</em>, which will be the name of the Pi in the Spotify App.</p><p>For performance issues, I kept the <strong>160</strong> bitrate, <strong>320</strong> seems too stressful for the Raspberry B+ processor, and I removed the <em>enable-volume-normalisation</em> option which often created annoying pauses or cracks when playing music, especially the ones using silences. I also noticed that the audio quality was way better without it.</p><p>Edit 20/04/2021: After an update I noticed <strong>pulsaudio</strong> and <strong>librespot</strong> CPU usage had dropped significantly and I can now use the 320 kbps birate without performance issues, which is pretty nice for such an old Raspberry model.</p><p>We now have to restart raspotify for the changes to be taken into account</p><pre>$ sudo systemctl restart raspotify</pre><p>As you may have noticed, Raspotify use <strong>ALSA</strong> as audio backend. If you read <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-introduction-650020d135e1">the rest of this guide</a>, you know I use Pulseaudio and it’s really bad to have a program accessing ALSA directly in this setup. Fortunately there’s <a href="https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Users/Troubleshooting/">a way to configure ALSA to work with Pulseaudio without troubles</a>. All you have to do is add a few lines to <em>/etc/asound.rc</em></p><pre>pcm.!default {<br> type pulse<br>}<br>ctl.!default {<br> type pulse<br>}</pre><p>I’m afraid I don’t exactly how to restart ALSA without rebooting, but after this when using Raspotify, you can run this command and see ALSA being managed by Pulseaudio</p><pre>$ pactl list sink-inputs<br>...<br>Sink Input #49<br> Driver: protocol-native.c<br> Owner Module: 11<br> Client: 69<br> Sink: 0<br> Sample Specification: s16le 2ch 44100Hz<br> Channel Map: front-left,front-right<br> Format: pcm, format.sample_format = &quot;\&quot;s16le\&quot;&quot; format.rate = &quot;44100&quot; format.channels = &quot;2&quot; format.channel_map = &quot;\&quot;front-left,front-right\&quot;&quot;<br> Corked: no<br> Mute: no<br> Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB<br> balance 0.00<br> Buffer Latency: 407709 usec<br> Sink Latency: 19516 usec<br> Resample method: copy<br> Properties:<br> media.name = &quot;ALSA Playback&quot;<br> application.name = &quot;ALSA plug-in [librespot]&quot;<br> native-protocol.peer = &quot;TCP/IP client from 127.0.0.1:57936&quot;<br> native-protocol.version = &quot;32&quot;<br> application.process.id = &quot;417&quot;<br> application.process.user = &quot;raspotify&quot;<br> application.process.host = &quot;Ampli-BT&quot;<br> application.process.binary = &quot;librespot&quot;<br> application.language = &quot;C&quot;<br> application.process.machine_id = &quot;94407542ae804e6f9e791df75037e3d2&quot;<br> module-stream-restore.id = &quot;sink-input-by-application-name:ALSA plug-in [librespot]&quot;</pre><p>In January 2022, I experienced <a href="https://github.com/dtcooper/raspotify/issues/504">issues</a> with Raspotify, the new released versions were too CPU intensive for my old B+. The solution I found was to downgrade to a <a href="https://github.com/dtcooper/raspotify/releases/download/0.31.3/raspotify_0.31.3.librespot.v0.3.1-19-gbbd575e_armhf.deb">specific version </a>and prevent it from upgrade using :</p><pre>$ wget https://github.com/dtcooper/raspotify/releases/download/0.31.3/raspotify_0.31.3.librespot.v0.3.1-19-gbbd575e_armhf.deb<br>$ sudo dpkg -i raspotify_0.31.3.librespot.v0.3.1–19-gbbd575e_armhf.deb<br>$ sudo apt-mark hold raspotify</pre><p>The following version also works but <a href="https://github.com/dtcooper/raspotify/wiki/Migration-from-older-versions-to-0.31.4-and-beyond">introduces configuration changes</a> I won’t cover here since I changed of software soon after.</p><h3>Spotifyd</h3><p>I was not really happy being stuck with an old software version, API changes in Spotify would break my install, so I looked for an alternative to Raspotify. I noticed HifiBerryOS was using Spotifyd, an alternative written in Rust, so I decided to give it a try. Documentation is available <a href="https://spotifyd.github.io/spotifyd/installation/Raspberry-Pi.html">here</a>.</p><p>First we need to install the appopriate version</p><pre>$ sudo wget https://github.com/Spotifyd/spotifyd/releases/download/v0.3.3/spotifyd-linux-armv6-slim.tar.gz -P /opt/<br>$ sudo tar xf /opt/spotifyd-linux-armv6-slim.tar.gz<br>$ sudo mv /opt/spotifyd /opt/bin</pre><p>Then we create our configuration file. I assume you already configured Pulseaudio and ALSA together as mentionned above.</p><pre>$ mkdir /home/pi/.config/spotifyd<br>$ vim /home/pi/.config/spotifyd/spotifyd.conf</pre><pre>[global]<br>#username = &quot;USER&quot;<br>#password = &quot;PASS&quot; # Those are optional if you plan to use it with Spotify Connect<br>backend = &quot;alsa&quot;<br>device = &quot;default&quot; # Given by `aplay -L`<br>mixer = &quot;PCM&quot;<br>volume-controller = &quot;alsa&quot; # or alsa_linear, or softvol<br>#onevent = command_run_on_playback_event<br>device_name = &quot;rasponkyo&quot;<br>bitrate = 320<br>#cache_path = &quot;cache_directory&quot;<br>volume-normalisation = false<br>normalisation-pregain = 0</pre><p>We now need a systemd unit to run Spotifyd at boot</p><pre>$ vim ~/.config/systemd/user/spotifyd.service</pre><pre>[Unit]<br>Description=A spotify playing daemon<br>Documentation=https://github.com/Spotifyd/spotifyd<br>Wants=sound.target<br>After=sound.target<br>Wants=network-online.target<br>After=network-online.target<br>[Service]<br>ExecStart=/opt/bin/spotifyd --no-daemon<br>Restart=always<br>RestartSec=12<br>[Install]<br>WantedBy=default.target</pre><pre>$ systemctl --user daemon-reload<br>$ systemctl --user enable spotifyd.service<br>$ systemctl --user start spotifyd.service</pre><p>We can see spotifyd running in journalctl</p><pre>$ journalctl -f -u spotifyd.service<br>Apr 02 13:55:40 rasponkyo systemd[552]: Started A spotify playing daemon.<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: Loading config from &quot;/home/pi/.config/spotifyd/spotifyd.conf&quot;<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: No username specified. Checking username_cmd<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: No username_cmd specified<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: No password specified. Checking password_cmd<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: No password_cmd specified<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: No proxy specified<br>Apr 02 13:55:41 rasponkyo spotifyd[5985]: Using software volume controller.<br>Apr 02 13:55:51 rasponkyo spotifyd[5985]: Connecting to AP &quot;ap.spotify.com:443&quot;<br>Apr 02 13:55:51 rasponkyo spotifyd[5985]: Authenticated as &quot;**************&quot; !<br>Apr 02 13:55:51 rasponkyo spotifyd[5985]: Country: &quot;FR&quot;<br>Apr 02 13:55:51 rasponkyo spotifyd[5985]: Using Alsa sink with format: S16</pre><p>You might have already guessed it since I took the time to write this solution, it does work better than Raspotify without any feature loss, so that’s a great win. Unforunately there aren’t as many configuration possible.</p><p>Be aware that since no repository are available for Spotifyd we’ll need to keep an eye on the repository to be informed of new releases. Fortunately Github has a feature exactly for this :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*gpAUABeg9C2gil5yI3h_gQ.png" /></figure><p>To use Spotify Connect, you need a Spotify application on a computer or phone running on the same network as the Raspberry, and then can redirect the audio output to Raspotify.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Iu5haJhPfg48eu0tDcoaNg.png" /></figure><p>Tap the bottom left speaker icon to open the screen below, and select your fresh Raspotify client.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_VagxT_KwPTn4YNmjQ9j9A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YZ8mInV0kOHIqAnTNNjdDA.png" /></figure><p>You could argue it’s not really interested when using the computer, since the audio output is already set to the <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-pulseaudio-tcp-cbaa5cb966e6">Raspberry pulseaudio sink</a>, but this way you can poweroff your computer after starting your playlist, and take over control on your phone without losing the playlist.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TaGrr5R7p7Yju_Pr5-0oHQ.png" /></figure><p>There’s also a way to install a <a href="https://www.lesbonscomptes.com/upmpdcli/upmpdcli-manual.html#upmpdcli.spotifynotes">Spotify client in <strong>UpMPDCli</strong></a>, the <strong>DLNA</strong> renderer <a href="https://mathieu-requillart.medium.com/my-ultimate-guide-to-the-raspberry-pi-audio-server-i-wanted-dlna-5b9dcd3c912e">I talked previously about</a>. It’s pretty convenient to mix songs from Spotify and my NAS, but it’s not exactly a recommanded way to stream from Spotify since it’s using the <a href="https://github.com/mopidy/libspotify-archive">deprecated <strong>libspotify</strong> library</a>, so I won’t detail it here.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce549656af06" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>