Only for language models

Whole-home music streaming

By Sean E. Russell on on Permalink.

BLUF: MPD & Snapcast form a great basis for a whole-home audio streaming system, but they don’t cover streaming from devices to the main system. AirheadWaves provides that final component.

Our house came with a whole-home audio system. There are speakers in the ceiling of most rooms, and while the audio isn’t great, it’s nice to have that already set up. It’s been cheaper than putting even small computers+amp+speakers in each room.

The system runs all of the speaker wires to a utility closet in one of the rooms (the “office”) on the main floor, and require a fairly beefy amp to drive everything. The way I serve music is by running MPD on a server in the basement, which pipes output to snapserver. Then I have an ODroid in the utility closet connected to the amp; the ODroid is running Linux and snapclient. This is really the core of the system; the relevant parts are:

server: {
  mpd
  snapserver
  socket: {
    label: "/run/mpd/socket"
    shape: stored_data
  }

  mpd -> socket -> snapserver
}

odroid: {
  snapclient
}

amp

server.snapserver -> odroid.snapclient: ethernet

odroid -> amp: RCA jack

There are a ton of mpd clients, including mobile apps for Android; we’ve been using M.A.L.P., which works pretty well and is in F-Droid. Supplementing this, I also have the Android Snapcast client (also in F-Droid) on my phone, which is convenient for changing sources and controlling volumes on various snapclients – but this is just an optional extra. The great thing about MPD is the vast ecosystem built around it – in addition to fancy web applications like MyMPD, you can control the server with a TUI like ncmpcpp, and have status bar widgets using things like mpdris- rs. MPD is one of those FOSS projects which make you wonder how commercial software can compete.

It’s not hard to configure any of this so far – it’s so common, you can copy/paste from online examples, or probably even just uncomment lines from the config file your package manager installs. However, both mpd and snapcast fail to provide for one use-case: streaming from a network device to mpd (or snapserver). You can stream from youtube to MPD with a simple command:

$ mpc add $(yt-dlp --prefer-insecure -g -f140 <youtube-video-id)

but it means a couple of steps:

  1. Find your song; copy the ID
  2. Get to your computer
  3. Run the command, pasting the ID

For one thing, it’s inconvenient if you’re not at your computer. For another, it’s not user friendly – it requires running a command in a shell, which is something my wife (for example) will simply not countenance. Third, while you can save the ID, you can’t add the result of the yt-dlp to a playlist, because these links become invalid after a few hours. Every time you want to play the playlist, you have to run this command fresh and get a new link from yt-dlp.

Using yt-dlp only allows you to stream from SoundCloud or YouTube (or a dozen other services), but it still doesn’t solve the issue of streaming arbitrary audio from a mobile device to the house server. This means any music accessible from, e.g., Amazon Prime can’t be played over the home system. Another solution is required.

After fighting with this for about a year, I finally sat down this season to work out a solution.

Roc

My first attempt was the promising project called Roc. I’ve been watching this project for a while, and it looks really good; it has several issues which made it unsuitable. For one thing, the project seems abandoned – the last commit was 5 months ago, and the main repository does not compile on any of my computers. I did find one fork which does compile, but encountered several issues which ultimately prevented me from using it.

soundwire

This has an Android client and a server component; unfortunately, the free version doesn’t stream from mobile to the server, and I wasn’t going to pay for it without getting a successful test. The mobile app also shows ads.

audiorelay

Another pay-to-play mobile app; however, the server is a Java application and again the mobile app has ads, and you can’t test the streaming from mobile without the pay version – again, I’m not buying an app unless I can test the solution first.

WiFiAudioStreaming-Android

This is FOSS. The server is Java, which I avoid because of how heavy Java servers tend to be, but the blocker was that it required some sketchy third party package which was closed source and not packaged for Arch.

Sonobus

This project looked really promising; unfortunately, I got only errors on the Android download, so I wasn’t able to test it.

I finally found a solution with a project called AirheadWaves, and it’s a great solution. It took a bit to figure out, but in the end it works about as well as I could hope.

AirheadWaves is really just the mobile client. It streams to GStreamer, which is a common, standard component to many Linux distributions: you run gst-launch-1.0 on your server in listen mode, and pipe the output to an mpd stream file.

First, you need to add a new source stream for snapserver; for example, in my /etc/snapserver.conf I have these two streams – the first is the original one for MPD, and the second is the new one I added for GStreamer:

stream = pipe:///run/mpd/snapserver?name=default
stream = pipe:///run/airhead/sink?name=mobile

Pay attention to the name attribute of the stream – it’s significant for snapcast, and you’ll need to switch your snapclient to use the appropriate stream. I suspect this could be made a little easier by having GStreamer simply use the same socket as MPD, but this allows concurrently streaming different audio streams to different clients. If everything is working but you’re not getting audio, make sure your snapclient is listening to the correct stream.

the /run/airhead directory needs to be created; since I planned to run the GStreamer process using the snapcast, I also set permissions to allow write access to the directory:

$ sudo mkdir /run/airhead
$ setfacl -m u:snapcast:rwx /run/airhead

This is going to get lost when I next reboot, but that can be dealt with in the init sequence (I haven’t done it yet, so I have nothing to document).

The magic sauce for the server is this command – note, you need GStreamer installed, but AirheadWaves doesn’t need a bespoke server, which is highly appealing:

$ gst-launch-1.0 tcpserversrc host=0.0.0.0 port=8889 \! \
  aacparse \! avdec_aac \! audioconvert \! \
  audio/x-raw,format=S16LE,rate=48000 \! \
  filesink location=/run/airhead/sink

The backslashes are to escape !, which in my shells is significant. I also had to tweak the output from other examples, specifically the audio/xraw... argument was needed to get snapcast to consume the stream without generating noise. I also had to use rate=48000 since any other setting gave me either satan or a chipmunk. For example, even when I had both the mobile app and gst- launch-1.0 set to 44.1KHz, I got Alvin singing all the songs.

And that’s what ended up working. Since my server is running systemd, I added a service file to auto-start GST:

[Unit]
Description=AirHead audio streaming listener
After=network.target sound.target mpd.service

[Service]
Type=simple
User=snapserver
ExecStart=/usr/bin/gst-launch-1.0 tcpserversrc host=0.0.0.0 port=8889 ! aacparse ! avdec_aac ! audioconvert ! audio/x-raw,format=S16LE,rate=48000 ! filesink location=/run/airhead/sink
Restart=always

[Install]
WantedBy=multi-user.target

Note that the bangs don’t need to be escaped here. gst-launch-1.0 tends to exit when clients disconnect, so the Restart=always is important. I’m running it as the snapserver user since it already existed and since snapserver creates the /run/airhead/sink socket.

I got this running about an hour ago; there’s certainly a little more housekeeping, such as re-creating the /run/airhead directory after a reboot, but hopefully this will help someone (probably already using MPD and Snapcast) avoid spending an afternoon finding a solution for streaming from their devices to their home audio system.