Setting up an Odroid C2/Raspberry Pi with librespot, PulseAudio and mopidy

Why?

I’ve got an old stereo sound system, which I only rarely use. But it’s not bad either. It’s just not the case, that I listen to the radio or CDs anymore. My CD collection is digitalized and most of the time, music gets distributed to me via Spotify.

But my laptop has poor sound and I’m always too lazy to pull the audio cable through the room and plug it into my laptop.

There has to be another a perfect solution

At best, let’s put a media center in front of my stereo system, which acts transitively to my laptop speakers and is able to play further without my laptop being online.

So for this, we’re going to use:

  • PulseAudio to act as a network sink (e.g. Watching YouTube videos on your laptop)
  • Spotify Connect to playback Spotify and be capable of controlling it even via a smartphone
  • mopidy to act as a music library manager on the digitized CDs

By occasion, I’ve got an Odroid C2 being around and a new USB sound card is quite cheap. You also could use a Raspberry Pi for this. You only may wonder, why I’ve chosen some paths, which are not reasonable on a Raspberry Pi, but I’ll mention these decisions later.

Setup

All steps are performed on the SERVER (here: Odroid C2/Raspberry Pi) unless otherwise specified.

PulseAudio

Let’s start with the precondition for everything: PulseAudio. You need it to actually make output to your stereo box.

Install the pulseaudio package and plug in your sound card. The sound card should be supported, but I guess these days it should be supported out of the box.

As we’re on a headless machine it’s undesired to have a PulseAudio server running per user session. We don’t have real user sessions on the Droid. So we configure PulseAudio to run as a global system service.

To do this, create the necessary pulse user for our system service first:

groupadd --system pulse-access
useradd --system -Umh /var/run/pulse -G pulse-access,audio pulse

Create the actual service:

# /etc/systemd/system/pulseaudio.service
[Unit]
Description=PulseAudio Sound Server

[Service]
Type=notify
ExecStart=/usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal
#User= <pulseaudio will drop privileges by itself>
Restart=always

[Install]
WantedBy=multi-user.target

A systemctl daemon-reload && systemctl enable --now pulseaudio should suffice to get PulseAudio running.

Test it via paplay <some audio file>. If something has gone south, go on at first and list your local audio sinks: pactl list sinks. If there is only a dummy output sink listed, it might be the unlikely case, your sound card isn’t supported.

Short not for later use: For every (system) user, which should be able to output sound later, you have to add it to the pulse-access group.

usermod -aG pulse-access <user>

Zeroconf

You can skip this, if the avahi-daemon service is already enabled.

Install the avahi package and run systemctl enable --now avahi-daemon.

This is desired, as this is the only way to cope with changing IP addresses.

PulseAudio as a network sink

Add to your system configuration from PulseAudio the following lines and make sure to adapt the local subnet ID in the auth-ip-acl parameter.

# /etc/pulse/system.pa
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.1.0/24 auth-anonymous=1
load-module module-rtp-recv
load-module module-zeroconf-publish

Do a systemctl restart pulseaudio to reflect your changes.

What does this do and why?

  • It spans a TCP server, which allows remote playback and control. You are now able to launch on your laptop PULSE_SERVER=<droidIP> pavucontrol to control the droid’s PulseAudio server. In theory it also could do playback over TCP, but you don’t want it.
  • It spans an additional RTP sink, which allows UDP connections. The reason to do this is simple: When you listen later to YouTube videos, the videostream won’t lag because some TCP packets aren’t acknowledged yet.
  • Last but not least, announce the server in the local net, that you don’t have a hassle with changing IP addresses.

librespot

Compilation

Install the packages git and rust. But first, add a dedicated Spotify user:

useradd --system -Umh /srv/spotify -G pulse-access spotify

And then login as the user and build the librespot package:

git clone https://github.com/librespot-org/librespot
cd librespot
cargo build --release -j1 --features ""

I had to add -j1 to my invocation, as cargo builds by default with all cores. But my power supply is not that strong. This occurred the first time to me there and I have to order a more powerful one.

Now, add the server script to /srv/spotify/server.sh and fill in your credentials. Make sure to quote your credentials right, if these contain non [a-zA-Z0-9] characters.

#!/bin/bash
# /srv/spotify/server.sh

set -euo pipefail

SPOTIFY_USER=
SPOTIFY_PASS=
# The name of your server to show up in other Spotify clients
# default: $(hostname)
SPOTIFY_HOST=
# Where do you want to save your spotify cache?
# default: cache is disabled
SPOTIFY_CACHEDIR=/srv/spotify/cache
# The bitrate, you want to stream with (possible: 320, 160, 96)
# default: 320
SPOTIFY_BITRATE=

BASE=$(dirname $(readlink -f $0))

$BASE/librespot/target/release/librespot \
                -u"${SPOTIFY_USER:?No Spotify User given}" \
                -p"${SPOTIFY_PASS:?No Spotify Password given}" \
                --name "${SPOTIFY_HOST:-$(hostname)}" \
                --bitrate "${SPOTIFY_BITRATE}" \
                ${SPOTIFY_CACHEDIR:+--cache "${SPOTIFY_CACHEDIR}"} \
                ${SPOTIFY_CACHEDIR:---disable-audio-cache} \
                --backend 'pipe' \
                --initial-volume 100 \
        | pacat \
                --latency-msec=100

And finally add the server process to /etc/systemd/system/spotify.service:

# /etc/systemd/system/spotify.service
[Unit]
Description=Spotify Connect Server

[Service]
ExecStart=/srv/spotify/server.sh
User=spotify
Restart=always
Requires=pulseaudio.service
After=pulseaudio.service

[Install]
WantedBy=multi-user.target

With systemctl enable --now spotify your host should show up on the

Support, comments, something else?

Feel free to leave a comment in my GitHub repo. Commenting articles and getting support via GitHub issues is a great solution. Also if it’s a specific problem to you. My GitHub repo does not resemble a code repo, where only real bugs are reported.

TODO

  • PulseAudio
    • Connect laptop speakers
    • Switch automatically sources to droid sink when connecting to network
    • Mask the PulseAudio user service
    • Support PulseAudio via IPv6, too?
  • librespot
    • reason, why librespot is necessary
  • mopidy
    • Install mopidy
    • Add run commands to librespot/mopidy to stop audio when playing from other source
  • Provide a TL;DR version