install snapserver - fcorthay/RPi-multiroom-audio GitHub Wiki

This documentation bases on the Snapcast documentation.

Table of Contents

Server

Installation

Multiroom audio is implemented through Snapcast.

In order to stream the music, install the Snap server:

sudo apt install -y snapserver
sudo chgrp users /etc/snapserver.conf
sudo chmod 664 /etc/snapserver.conf

Allow streaming to both IPV4 and IPV6:

ORIGINAL='#bind_to_address = 0.0.0.0'
REPLACEMENT="$ORIGINAL\nbind_to_address = ::"
sudo sed -i "s/$ORIGINAL/$REPLACEMENT/" /etc/snapserver.conf

Audio path redirection

The basic source for snapserver is a FIFO. However we will be using the capture from ALSA, using the loopback device.

The reason for this is that one cannot mix mopidy together with the snapcast receiver, snapclient, onto the same soundcard, using the ALSA dmix mechanism (at least not to my knowledge). The solution to this is to disconnect mopidy from the soundcard, connect it to the ALSA Loopback device, subdevice SNAPSERVER_LOOPBACK_SUBDEVICE, have snapserver stream this audio to the Ethernet port, and have snapclient receive it and send it to the soundcard. This audio path also allows other devices on the same Ethernet network to receive and play the same music as the RPi.

If we consider to later also redirect the snapclient output via the Loopback, in order to have some signal processing right before the loudspeakers, the subdevice SNAPSERVER_LOOPBACK_SUBDEVICE, defined in configuration.bash, should not be 0 as snapclient only uses subdevice 0 (at least as to my knowledge). Hence the choice of the subdevice 1.

With this, we can change the audio stream source in the snapserver configuration:

source ~/Documents/RPi-multiroom-audio/configuration.bash
ORIGINAL='source = pipe:///tmp/snapfifo?name=default'
REPLACEMENT="#$ORIGINAL\nsource = alsa:///?name=Loopback&device=hw:$ALSA_LOOPBACK_PLAYBACK_DEVICE,$SNAPSERVER_LOOPBACK_SUBDEVICE"
ORIGINAL=${ORIGINAL////\\/}
REPLACEMENT=${REPLACEMENT//&/\\&}
REPLACEMENT=${REPLACEMENT////\\/}
sudo sed -i "s/$ORIGINAL/$REPLACEMENT/" /etc/snapserver.conf

Final setup

Change the audio sample format to math the one in the configuration:

source ~/Documents/RPi-multiroom-audio/configuration.bash
ORIGINAL='#sampleformat = 48000:16:2'
REPLACEMENT="$ORIGINAL\nsampleformat = $AUDIO_RATE:$AUDIO_BIT_NB:2"
sudo sed -i "s/$ORIGINAL/$REPLACEMENT/" /etc/snapserver.conf

Add the snapserver user to the audio group:

SNAPSERVER_USER=`cat /lib/systemd/system/snapserver.service | grep ^User | cut -d '=' -f 2`
sudo usermod -a -G audio $SNAPSERVER_USER
cat /etc/group | grep ^audio

Restart the service:

sudo service snapserver restart

Check how it is working:

ps aux | grep snap | grep -v grep
sudo service snapserver status | cat

Mopidy output redirection

Connect the mopidy output to the Loopback device:

source ~/Documents/RPi-multiroom-audio/configuration.bash
ORIGINAL="output = alsasink device=dmix:$AMPLIFIER_SOUNDCARD"
REPLACEMENT="#$ORIGINAL\noutput = audioresample ! audioconvert"
REPLACEMENT+=" ! audio\/x-raw,rate=$AUDIO_RATE,channels=2,format=S${AUDIO_BIT_NB}LE"
REPLACEMENT+=" ! alsasink device=dmix:$ALSA_LOOPBACK_CAPTURE_DEVICE,$SNAPSERVER_LOOPBACK_SUBDEVICE"
sudo sed -i "s/$ORIGINAL/$REPLACEMENT/" /etc/mopidy/mopidy.conf
sudo service mopidy restart

With this, the mopidy audio should now pay on the audio receiver device.

Test

Audio file streaming

For a quick test, install Snapcast on Android or Snapcast on iOS.

Play a file on the Loopback device :

alias play2loop="SDL_AUDIODRIVER='alsa' AUDIODEV='dmix:$ALSA_LOOPBACK_CAPTURE_DEVICE,$SNAPSERVER_LOOPBACK_SUBDEVICE' ffplay -ar $AUDIO_RATE -autoexit -nodisp -loglevel quiet"
play2loop ~/Music/someFile.flac

Check the RPi's address:

ifconfig | grep -A 2 eth0

Specify the snapcast server on the mobile device accordingly and play what is being streamed.

Remote control

Snapcast can be controlled per JavaScript Object Notation - Remote Procedure Call (JSON-RPC).

For complex objects to be displayed in a terminal, install jq:

sudo apt install -y jq

Command line

Making a JSON-RPC request can be done using curl in a terminal.

The Snapcast Application Programming Interface (API) is documented in the GitHub documentation.

Getting the server status allows, among others, to get the client ids:

curl -s -d '{"jsonrpc":"2.0", "id":1, "method":"Server.GetStatus"}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc | jq

The client ids seem to be their MAC address. The client locally playing has "id": "00:00:00:00:00:00".

Get a client's volume:

curl -s -d '{"jsonrpc":"2.0", "id":1, "method":"Client.GetStatus", "params":{"id":"00:00:00:00:00:00"}}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc | jq | grep -A 3 volume

Set a client's volume and mute status:

curl -sw "\n" -d '{"jsonrpc":"2.0", "id":1, "method":"Client.SetVolume", "params":{"id":"00:00:00:00:00:00", "volume":{"muted":false, "percent":75}}}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc

Set a client's volume only:

curl -sw "\n" -d '{"jsonrpc":"2.0", "id":1, "method":"Client.SetVolume", "params":{"id":"00:00:00:00:00:00", "volume":{"percent":50}}}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc

Mute a client's sound (adapt id):

curl -sw "\n" -d '{"jsonrpc":"2.0", "id":1, "method":"Client.SetVolume", "params":{"id":"00:00:00:00:00:00", "volume":{"muted":true}}}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc

Remove a client (adapt id):

curl -sw "\n" -d '{"jsonrpc":"2.0", "id":1, "method":"Server.DeleteClient", "params":{"id":"00:00:00:00:00:00"}}' -H 'Content-Type: application/json' http://localhost:1780/jsonrpc

Python

Here a Python example for a Snapcast request:

#!/usr/bin/env python3
import json
import requests

SNAP_SERVER_NAME='localhost'
SNAP_SERVER_PORT=1780

url = "http://%s:%d/jsonrpc" % (SNAP_SERVER_NAME, SNAP_SERVER_PORT)
request_id = 0
payload = {'jsonrpc': '2.0', 'id': request_id}

payload['method'] = 'Server.GetStatus'
response = requests.post(url, data=json.dumps(payload)).json()
request_id = request_id + 1

groups_info = response['result']['server']['groups']
for group_info in groups_info :
    for client_info in group_info['clients'] :
        client_name = client_info['host']['name']
        client_id = client_info['id']
        print("%s : %s" % (client_name, client_id))

In the repository:

  • Snapcast/getStatus.bash performs the above request with additional bells and whistles
  • Snapcast/setVolume.bash allows to change the volume of one of the snapserver's clients
To easily use them, one can add the following lines in ~/.bash_aliases:
source ~/Documents/RPi-multiroom-audio/configuration.bash

alias getsnapserver='SNAPCAST_SERVER=`cat /etc/default/snapclient | grep ^SNAPCLIENT_OPTS | sed "s/.*--host\s*//" | sed "s/[ \"].*//"`'
alias volume='getsnapserver && $AUDIO_BASE_DIR/Snapcast/setVolume.py -s $SNAPCAST_SERVER $(hostname)'
alias snapstatus='getsnapserver && $AUDIO_BASE_DIR/Snapcast/getStatus.py -s $SNAPCAST_SERVER'

Scripts

Check the audio path:

source ~/Documents/RPi-multiroom-audio/configuration.bash
$AUDIO_BASE_DIR/Management/showInstallation.bash

See the devices served:

source ~/Documents/RPi-multiroom-audio/configuration.bash
$AUDIO_BASE_DIR/Snapcast/getStatus.py

Change the volume on one of the devices:

source ~/Documents/RPi-multiroom-audio/configuration.bash
$AUDIO_BASE_DIR/Snapcast/setVolume.py AudioAmp 50

Installation

The present installation is presented hereafter:

Mopidy reads music from the disk and sends it to the the loopback device. Snapserver reads music from the loopback device and sends it to the Ethernet port. And indeed : there is no more sound from the RPi, yet one can listen to the music on another device.

Continue the installation process.

⚠️ **GitHub.com Fallback** ⚠️