Storytime
This post is really about high quality audio streaming over the network, but PlexAmp is the motivator.
A gap in my desktop computing experience on FreeBSD is how I can listen to my audio library the same way I used to on MacOS, via the incredible PlexAmp software. This client on Desktop is only available on Windows, MacOS, and Linux via a Flatpak. I don’t want to digress over the reasons why I specifically want this software, but it currently doesn’t work via the FreeBSD Linuxulator on 14.3-RELEASE and I haven’t had a chance to test the upcoming 15.0-RELEASE.
There is a PlexAmp Headless release that runs on Linux amd64, armv7, or arm64 platforms. This is meant to be used with an embedded device and a DAC to allow you to…
Storytime
This post is really about high quality audio streaming over the network, but PlexAmp is the motivator.
A gap in my desktop computing experience on FreeBSD is how I can listen to my audio library the same way I used to on MacOS, via the incredible PlexAmp software. This client on Desktop is only available on Windows, MacOS, and Linux via a Flatpak. I don’t want to digress over the reasons why I specifically want this software, but it currently doesn’t work via the FreeBSD Linuxulator on 14.3-RELEASE and I haven’t had a chance to test the upcoming 15.0-RELEASE.
There is a PlexAmp Headless release that runs on Linux amd64, armv7, or arm64 platforms. This is meant to be used with an embedded device and a DAC to allow you to stream directly to your stereo or home theater system. I have one of these running on a HiFiBerry, so I always have a PlexAmp “player” on my network. Perhaps I could get this slimmed down PlexAmp client running with the FreeBSD Linuxulator as it’s a NodeJS service with a web interface instead of an Electron-based desktop app, but I’d rather have the full desktop app and I think it will be feasible in the near future so I haven’t gone this route.
Getting To The Point
Ok, I’m getting sidetracked. So I had this device and my hack at being able to stream audio from this was to use ffmpeg or another tool to stream the audio to an Icecast server I already had running. This worked, but you’re not going to get super high quality audio out of Icecast and even on a LAN the buffering is a pain. It acts really weird when you leave the stream running but the player was paused. The first time you connect you get many minutes of silence buffered instantly which is weird. This is really not a good use case for Icecast. I want some way to stream high quality audio with very low latency to my FreeBSD desktop. I don’t need a fancy GUI for any of this either, I just need something that works.
High Quality Audio Streaming Options
The important part here is high quality. I don’t want to encode to something lossy because I’m streaming this over the LAN.
I brainstormed through the available options and they weren’t very enticing:
- PulseAudio, which everyone is cursed with
- JACK, which might be more lightweight for the HiFiBerry
- SoX and/or ffmpeg could be cobbled together and do FLAC, unsure on reliability
- trx seemed plausible solution at first but it uses Opus.
- sndiod from OpenBSD is ported to FreeBSD and Linux so it might work pretty well, but it doesn’t seem like the network part is really a thoroughly polished feature and I found this comment in the man page BUGS section on Debian:
BUGS
Resampling is low quality; down-sampling especially should be avoided when recording.
Processing is done using 16-bit arithmetic, thus samples with more than 16 bits are
rounded. 16 bits (i.e. 97dB dynamic) are largely enough for most applications though.
Processing precision can be increased to 24-bit at compilation time though.
and in the latest release:
BUGS
Resampling is low quality; down-sampling especially should be avoided
when recording
This wasn’t super promising.
Roc Toolkit
And then a friend mentioned a tool I wasn’t aware of called roc-toolkit. This is a tool that is really designed to do exactly what I wanted: high quality audio, low latency streaming, and as a bonus it’s designed to work reliably in poor network conditions. This may one day become the recommended tool for any type of audio streaming: multi-room like Sonos/Snapcast, direct streaming on a private network, or even a new generation of streaming radio over the internet. Pretty cool. It’s currently going to be a 16bit 44100hz stream, but that’s CD-quality so I consider it good enough if I’m not playing the FLAC directly. I’ll still have the highest quality playing through my stereo when I want.
Unfortunately this tool wasn’t in FreeBSD ports and their GitHub indicated it might not even compile yet, but I sorted that out and have a working version on FreeBSD by twiddling with their Scons build and porting their forked OpenFEC library so there are no missing dependencies when it does get packaged.
Solution
Here are the important bits. I followed their docs to build and install on Debian which is running on my HiFiBerry and then came up with this ALSA config that will duplicate audio being played through the DAC to a Loopback device at 44100hz and 16bit.
# /etc/asound.conf
defaults.pcm.dmix.!rate 44100
defaults.pcm.dmix.!format S16_LE
pcm.multi {
type multi
slaves.a.pcm "hw:sndrpihifiberry,0"
slaves.a.channels 2
slaves.b.pcm "dmix:Loopback"
slaves.b.channels 2
bindings.0 { slave a; channel 0; }
bindings.1 { slave a; channel 1; }
bindings.2 { slave b; channel 0; }
bindings.3 { slave b; channel 1; }
}
pcm.both {
type route
slave.pcm "multi"
ttable.0.0 1
ttable.1.1 1
ttable.0.2 1
ttable.1.3 1
}
pcm.!default {
type asym
playback.pcm "plug:both"
capture.pcm "plug:dsnoop:PCH"
}
pcm.loop "plug:\"dsnoop:Loopback,1\""
I’m still not sure if this is the most optimal ALSA configuration as I wonder if I should really be explicitly setting the rate and format or if the dmix part is even necessary. Could it work without? Could roc-send detect when the device changes rate and format and just work? Who knows! I’ll try to find out.
Now in a tmux session (until the rocd daemon on their roadmap is checked off), I run this script:
#!/bin/sh
# Broadcast to the whole LAN!
# but at what cost?
#DEST=10.27.3.255
DEST=10.27.3.2
DEVICE=alsa://hw:Loopback,1,0
roc-send -v -i ${DEVICE} -s rtp+rs8m://${DEST}:10001 \
-r rs8m://${DEST}:10002 -c rtcp://${DEST}:10003
On my FreeBSD desktop I run:
#!/bin/sh
# I call it ~/bin/radio
LISTEN=0.0.0.0
roc-recv -v -o oss://default -s rtp+rs8m://${LISTEN}:10001 \
-r rs8m://${LISTEN}:10002 -c rtcp://${LISTEN}:10003
And now I get the low latency but high quality audio coming through my headphones+DAC hooked up to my FreeBSD desktop. The CPU usage is also quite low which was one of my concerns about introducing the whole PulseAudio stack onto the HiFiBerry. The fewer layers involved the better as well – for all I know I’d have to fight with PulseAudio downsampling too when it runs into something like a 88200hz 24bit FLAC that I sometimes end up with in my library.
There is some support for Multicast support but I haven’t pieced that together yet as my initial attempt didn’t work. If I can get that to work I’ll update this post because Multicast would be much better as any device on the network could opt-in instead of doing Broadcast and blasting the network with audio. I’ve tried it and it works, but I also wonder how many devices will fall over from this.
Mission accomplished though. I have to control the music with my phone or via the PlexAmp Headless web interface, but it works great with almost instant feedback to the audio when I play/pause/skip tracks, etc.