Skip to content

Commit

Permalink
multiroom: update snapcast to v0.22 and various improvements
Browse files Browse the repository at this point in the history
- Update snapcast to v0.22.0-r0
- Replace FIFO stream source with ALSA which significantly reduces CPU and I/O usage (fixes #294)
- Add SOUND_INPUT_LATENCY and SOUND_OUTPUT_LATENCY env vars (also helps with #294)
- Reduce multiroom-server image with multi stage builds for a slimmer image
- Reduce verbose level for both multiroom services

Connects-to: #294
Change-type: minor
Signed-off-by: Tomás Migone <tomas@balena.io>
  • Loading branch information
tmigone committed Nov 23, 2020
1 parent 2086ee6 commit b7ba5b5
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 21 deletions.
6 changes: 3 additions & 3 deletions core/audio/balena-sound.pa
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
# Create balena-sound sinks
load-module module-null-sink sink_name=balena-sound.input
load-module module-null-sink sink_name=balena-sound.output
load-module module-pipe-sink file=/var/cache/snapcast/snapfifo sink_name=snapcast format=s16le rate=44100
load-module module-null-sink sink_name=snapcast

# Route audio internally, loopback sinks depend on configuration. See start.sh for details:
# balena-sound.input: For multiroom it's routed to snapcast sink, for standalone directly wired to balena-sound.output
# balena-sound.output: Set to audio sink specified by audio block
load-module module-loopback source="balena-sound.input.monitor" %INPUT_SINK%
load-module module-loopback source="balena-sound.output.monitor" %OUTPUT_SINK%
load-module module-loopback latency_msec=%INPUT_LATENCY% source="balena-sound.input.monitor" %INPUT_SINK%
load-module module-loopback latency_msec=%OUTPUT_LATENCY% source="balena-sound.output.monitor" %OUTPUT_SINK%

# Route all plugin input to the default sink
set-default-sink balena-sound.input
14 changes: 14 additions & 0 deletions core/audio/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ set -e
CONFIG_TEMPLATE=/usr/src/balena-sound.pa
CONFIG_FILE=/etc/pulse/balena-sound.pa

# Set loopback module latency
function set_loopback_latency() {
local LOOPBACK="$1"
local LATENCY="$2"

sed -i "s/%$LOOPBACK%/$LATENCY/" "$CONFIG_FILE"
}

# Route "balena-sound.input" to the appropriate sink depending on selected mode
# Either "snapcast" fifo sink or "balena-sound.output"
function route_input_sink() {
Expand Down Expand Up @@ -72,11 +80,17 @@ while ! curl --silent --output /dev/null "$SOUND_SUPERVISOR/ping"; do sleep 5; e
SOUND_SUPERVISOR="$(ip route | awk '/default / { print $3 }'):3000"
MODE=$(curl --silent "$SOUND_SUPERVISOR/mode" || true)

# Get latency values
SOUND_INPUT_LATENCY=${SOUND_INPUT_LATENCY:-200}
SOUND_OUPUT_LATENCY=${SOUND_OUTPUT_LATENCY:-200}

# Audio routing: route intermediate balena-sound input/output sinks
echo "Setting audio routing rules. Note that this can be changed after startup."
reset_sound_config
route_input_sink "$MODE"
route_output_sink
set_loopback_latency "INPUT_LATENCY" "$SOUND_INPUT_LATENCY"
set_loopback_latency "OUTPUT_LATENCY" "$SOUND_OUPUT_LATENCY"
if [[ -n "$SOUND_ENABLE_SOUNDCARD_INPUT" ]]; then
route_input_source
fi
Expand Down
8 changes: 5 additions & 3 deletions core/multiroom/client/Dockerfile.template
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine:3.12
# Minimum snapcast version for ALSA stream source is v0.21
# Currently Alpine 3.12 is pinned to snapcast v0.19 so we need to use Alpine edge
FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine:edge
WORKDIR /usr/src

RUN install_packages snapcast-client

# Audio block setup
RUN curl --silent https://raw.githubusercontent.com/balenablocks/audio/master/scripts/alsa-bridge/alpine-setup.sh | sh
ENV PULSE_SERVER=tcp:audio:4317
ENV PULSE_SINK=balena-sound.output
RUN curl --silent https://raw.githubusercontent.com/balenablocks/audio/master/scripts/alsa-bridge/alpine-setup.sh | sh

WORKDIR /usr/src
COPY start.sh .

CMD [ "/bin/bash", "/usr/src/start.sh" ]
5 changes: 1 addition & 4 deletions core/multiroom/client/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ echo "Target snapcast server: $SNAPSERVER"

# Start snapclient
if [[ "$MODE" == "MULTI_ROOM" || "$MODE" == "MULTI_ROOM_CLIENT" ]]; then
# Start snapclient and filter out those pesky chunk logs
# grep filter can be removed when we get snapcast v0.20
# see: https://github.com/badaix/snapcast/issues/559#issuecomment-615874719
/usr/bin/snapclient --host $SNAPSERVER $LATENCY | grep -v "\[Info\] (Stream) Chunk"
/usr/bin/snapclient --host $SNAPSERVER $LATENCY --logfilter *:notice
else
echo "Multi-room client disabled. Exiting..."
exit 0
Expand Down
28 changes: 23 additions & 5 deletions core/multiroom/server/Dockerfile.template
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine:3.12
# Build snapweb separately
FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine-node:latest as web-builder
WORKDIR /usr/src

RUN install_packages git make npm

RUN git clone https://github.com/badaix/snapweb.git snapweb
RUN npm install --global --no-save typescript
RUN cd snapweb && make

# Minimum snapcast version for ALSA stream source is v0.21
# Currently Alpine 3.12 is pinned to snapcast v0.19 so we need to use Alpine edge
FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine:edge
WORKDIR /usr/src

RUN install_packages snapcast-server git make npm
# Install snapweb
RUN mkdir -p /var/www
COPY --from=web-builder /usr/src/snapweb/dist/* /var/www/

# Install snapcast
RUN install_packages snapcast-server
COPY snapserver.conf /etc/snapserver.conf
COPY start.sh .
RUN git clone https://github.com/badaix/snapweb.git snapweb
RUN npm install --global --no-save typescript
RUN cd snapweb && make && mkdir -p /var/www && mv dist/* /var/www

# Audio block setup
ENV PULSE_SERVER=tcp:audio:4317
ENV PULSE_SOURCE=snapcast.monitor
RUN curl --silent https://raw.githubusercontent.com/balenablocks/audio/master/scripts/alsa-bridge/alpine-setup.sh| sh

CMD [ "/bin/bash", "/usr/src/start.sh" ]
4 changes: 3 additions & 1 deletion core/multiroom/server/snapserver.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ port = 1780
doc_root = /var/www/

[stream]
stream = pipe:///var/cache/snapcast/snapfifo?name=balenaSound
stream = alsa://?name=balenaSound&device=pulse
sampleformat = 44100:16:2

[logging]
filter = *:notice
5 changes: 0 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ version: '2'

volumes:
spotifycache:
snapcast:

services:

Expand All @@ -14,8 +13,6 @@ services:
io.balena.features.dbus: 1
ports:
- 4317:4317
volumes:
- snapcast:/var/cache/snapcast

sound-supervisor:
build: ./core/sound-supervisor
Expand All @@ -32,8 +29,6 @@ services:
- 1704:1704
- 1705:1705
- 1780:1780
volumes:
- snapcast:/var/cache/snapcast

multiroom-client:
build: ./core/multiroom/client
Expand Down
3 changes: 3 additions & 0 deletions docs/03-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ The following environment variables apply to balenaSound in general, modifying i
| SOUND_VOLUME | Output volume level at startup. | 0 - 100, integer value without the `%` symbol. | 75 |
| SOUND_DEVICE_NAME / BLUETOOTH_DEVICE_NAME | Device name to be advertized by plugins (AirPlay device list, Spotify Connect and UPnP). For bluetooth use `BLUETOOTH_DEVICE_NAME` | Any valid string. | `balenaSound <plugin> <xxxx>`, where:<br>- `<plugin>` is `Spotify, AirPlay, UPnP`<br>- `<xxxx>` the first 4 chars of the device UUID. |
| AUDIO_OUTPUT | Select the default audio output interface. See [audio block](https://github.com/balenablocks/audio/blob/master/README.md#environment-variables). | For all device types: <br>- `AUTO`: Automatic detection. Priority is `USB > DAC > HEADPHONES > HDMI`<br>- `DAC`: Force default output to be an attached GPIO based DAC<br><br> For Raspberry Pi devices: <br>- `RPI_AUTO`: Official BCM2835 automatic audio switching as described [here](https://www.raspberrypi.org/documentation/configuration/audio-config.md) <br>- `RPI_HEADPHONES`: 3.5mm audio jack <br>- `RPI_HDMI0`: Main HDMI port <br>- `RPI_HDMI1`: Secondary HDMI port (only Raspberry Pi 4) <br><br> For Intel NUC: <br>- NUCs have automatic output detection and switching. If you plug both the HDMI and the 3.5mm audio jack it will use the latter. | `AUTO` |
| SOUND_INPUT_LATENCY | Input loopback latency in milliseconds. Useful when experiencing frequent audio stuttering due to underruns. Note that this is only a friendly request, the actual latency might be higher. | 1 - 2000. | 200 |
| SOUND_OUTPUT_LATENCY | Output loopback latency in milliseconds. Note that this is only a friendly request, the actual latency might be higher. | 1 - 2000. | 200 |


## Multi-room

Expand Down

0 comments on commit b7ba5b5

Please sign in to comment.