Share This
//Streaming Vynil On Sonos

Streaming Vynil On Sonos

Mixup some Vyinl Oldies, a little Ice to cast, and a PI for some energy, and your ready to go

A little known trivia - I was once a Disc Jokey, and spent a lot of my youth behind the decks, in clubs around the West Of Ireland. Today, I still am the proud owner of a very large collection of Vynil and CD music, which of course deserves to get a second life with my digital streaming audio system powered by Sonos

USB Turntable Streamer

I own a really nice turntable which is modeled on the Legendary Technical SL1200 MK3, which I am so well aquatinted with, including the awesome Citronix DJ Console which was home to 2 of these beauties in so many clubs way back when…

My Audio-Technica AT-LP120-USB device is the focus of todays IoT challange, I will be using a Raspberry PI3, to stream audio from one of these turntables with USB audio codec output.

If your in the market, these are also workable options for this exercise

Enable SSH before booting

Because we are going to use the Raspberry Pi headless (without a display) and without keyboard attached, we need a way to control the device. Luckily we can enable SSH by adding an empty file called ssh to the root of the SD card. This will enable SSH for us automatically. If you are using the Ethernet port on the Raspberry Pi, networking and SSH should work out of the box with DHCP.

Update to Current OS

Update your Raspbian install:

sudo apt-get update

Connect the USB Turntable to the Raspberry Pi

Now, connect the turntable to the Raspberry Pi, using USB. You can use the command arecord -l to check if your device has been detected. Mine shows this:

[email protected]:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: CODEC [USB AUDIO  CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

Make a note of the card number, 1 in my case. This is probably the same for you, but if it differs, you may need to remember it and change accordingly in the following steps.

Fix volume issues

As most USB turntables do not have hardware volume control, and the input volume is stuck on roughly half of what it should be, we need to add a software volume control. Create the file /etc/asound.conf and edit it to add the following contents:

pcm.dmic_hw {
    type hw
    card 1
    channels 2
    format S16_LE
}
pcm.dmic_mm {
    type mmap_emul
    slave.pcm dmic_hw
}
pcm.dmic_sv {
    type softvol
    slave.pcm dmic_hw
    control {
        name "Boost Capture Volume"
        card 1
    }
    min_dB -5.0
    max_dB 20.0
}

Next, run this command to refresh the alsa state and also show VU Meters to test the input volume:

arecord -D dmic_sv -r 44100 -f S16_LE -c 2 --vumeter=stereo /dev/null

As you might notice, the volume is way too low. You can use alsamixer to change the volume. Press F6 to select the USB Turntable device, and press TAB until you see the boost slider.

I have it set to 65 on my setup, but you might try out. Make sure you are not turning it up too high, or your sound quality might degrade due to clipping.

icecast2

Now, to stream we will use the icecast2 package, which of course needs to be deployed to our Pi.

sudo apt-get install icecast2

Configure icecast2

Next, we will edit /etc/icecast2/icecast.xml, which is the casting servers settings, to provide a name for the stream, and some credential’s to protect the stream.

<icecast>

    <location>Record Room</location>
    <admin>[email protected]</admin>

    <limits>
        <clients>100</clients>
        <sources>2</sources>
        <queue-size>524288</queue-size>
        <client-timeout>30</client-timeout>
        <header-timeout>15</header-timeout>
        <source-timeout>10</source-timeout>
        <burst-on-connect>0</burst-on-connect>
        <burst-size>65535</burst-size>
    </limits>

    <authentication>
        <source-password>vynil</source-password>
        <relay-password>vynil</relay-password>

        <!-- Admin logs in with the username given below -->
        <admin-user>admin</admin-user>
        <admin-password>vynil</admin-password>
    </authentication>


    <hostname>localhost</hostname>

    <listen-socket>
        <port>80</port>
    </listen-socket>

    <http-headers>
        <header name="Access-Control-Allow-Origin" value="*" />
    </http-headers>


    <fileserve>1</fileserve>

    <paths>
        <basedir>/usr/share/icecast2</basedir>

        <logdir>/var/log/icecast2</logdir>
        <webroot>/usr/share/icecast2/web</webroot>
        <adminroot>/usr/share/icecast2/admin</adminroot>
        <alias source="/" destination="/status.xsl"/>
        <!-- The certificate file needs to contain both public and private part.
             Both should be PEM encoded.
        <ssl-certificate>/usr/share/icecast2/icecast.pem</ssl-certificate>
        -->
    </paths>

    <logging>
        <accesslog>access.log</accesslog>
        <errorlog>error.log</errorlog>
        <loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
        <logsize>10000</logsize> <!-- Max size of a logfile -->
    </logging>

    <security>
        <chroot>0</chroot>
    </security>
</icecast>

Starting IceCast2

We are going to start the casting server, but we will bing to TCP 80, which requires that we allow this to happen, but following these simple steps.

sudo setcap 'cap_net_bind_service=+ep' `which icecast2`

update-rc.d icecast2 defaults
systemctl status icecast2.service

Icecast2 admin

Now, we should have the casting server online, and working, It will be listening at http:\\Pi (replacing PI with the IP or name you assigned to the device) and is good for checking the status of connected clients, authenticate with the account admin and password vynil

Darkice

Now we need to link the USB audio source, to our Icecast server, and to make this work, we will use another grate package called darkice

Then install a bunch of needed packages:

sudo apt-get -y install darkice

Configure Darkice

This time, we will update the DarkIce configuration file located at /etc/darkice.cfg so that it is aware of where the USB turntable is connected to our system (remember the pointer earlier), and how to connect with our Icecast server.

# this section describes general aspects of the live streaming session
[general]
duration        = 0         # duration of encoding, in seconds. 0 means forever
bufferSecs      = 1         # size of internal slip buffer, in seconds
reconnect       = yes       # reconnect to the server(s) if disconnected
realtime        = yes       # run the encoder with POSIX realtime priority
rtprio          = 3         # scheduling priority for the realtime threads

# this section describes the audio input that will be streamed
[input]
device          = hw:1,0    # OSS DSP soundcard device for the audio input
sampleRate      = 44100     # other settings have crackling audo, esp. 44100
bitsPerSample   = 16        # bits per sample. try 16
channel         = 2         # channels. 1 = mono, 2 = stereo

# this section describes a streaming connection to an IceCast2 server
# there may be up to 8 of these sections, named [icecast2-0] ... [icecast2-7]
# these can be mixed with [icecast-x] and [shoutcast-x] sections
[icecast2-0]
bitrateMode     = cbr
format          = mp3
bitrate         = 320
server          = localhost
port            = 80
password        = vynil
mountPoint      = listen.mp3
name            = Turntable
description     = Audio-Technica AT-LP120 Turntable
url             = http://turntable
genre           = vinyl
public          = no
localDumpFile   = recording.m4a

Darkice Init Script

Ok, we now need to create a simple script which will autostart this darkice service each time the Pi is rebooted, so that we can set and forget about this little IoT solution.

Start by creating a new init.d file called /etc/init.d/darkice and then populating the file with the following script

#!/bin/sh
#
# Copyright (c) 2007 Javier Fernandez-Sanguino <[email protected]>
# Copyright (c) 2009 Jochen Friedrich <[email protected]>
#
# This is free software; you may redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2,
# or (at your option) any later version.
#
# This is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License with
# the Debian operating system, in /usr/share/common-licenses/GPL;  if
# not, write to the Free Software Foundation, Inc., 59 Temple Place,
# Suite 330, Boston, MA 02111-1307 USA
#
### BEGIN INIT INFO
# Provides:          darkice
# Required-Start:    $network $local_fs $remote_fs
# Required-Stop:     $network $local_fs $remote_fs
# Should-Start:
# Should-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Live audio streamer
# Description:       DarkIce is an IceCast, IceCast2 and ShoutCast
#                    live audio streamer.
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

NAME=darkice
DAEMON=/usr/bin/$NAME
DESC="Live audio streamer"
LOGDIR=/var/log
USER=nobody
GROUP=nogroup
LOGFILE="$LOGDIR/$NAME.log"

PIDFILE=/var/run/$NAME.pid

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

# Default options, these can be overriden by the information
# at /etc/default/$NAME
DAEMON_OPTS=""          # Additional options given to the server

DIETIME=2               # Time to wait for the server to die, in seconds
                        # If this value is set too low you might not
                        # let some servers to die gracefully and
                        # 'restart' will not work

# Include defaults if available
if [ -f /etc/default/$NAME ] ; then
	. /etc/default/$NAME
fi

# Use this if you want the user to explicitly set 'RUN' in
# /etc/default/
if [ "x$RUN" != "xyes" ] ; then
    exit 0
fi

set -e

running_pid() {
# Check if a given process pid's cmdline matches a given name
    pid=$1
    name=$2
    [ -z "$pid" ] && return 1
    [ ! -d /proc/$pid ] &&  return 1
    cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
    # Is this the expected server
    [ "$cmd" != "$name" ] &&  return 1
    return 0
}

running() {
# Check if the process is running looking at /proc
# (works for all users)
    sleep 1
    # No pidfile, probably no daemon present
    [ ! -f "$PIDFILE" ] && return 1
    pid=`cat $PIDFILE`
    running_pid $pid $DAEMON || return 1
    return 0
}

start_server() {
# Start the process using the wrapper

        start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
            --background --chuid $USER:$GROUP --no-close \
	    --exec $DAEMON -- $DAEMON_OPTS >> $LOGFILE 2>&1

        errcode=$?
	return $errcode
}

stop_server() {
# Stop the process using the wrapper
        start-stop-daemon --stop --quiet --remove-pidfile --pidfile $PIDFILE \
            --exec $DAEMON
        errcode=$?
	return $errcode
}

force_stop() {
# Force the process to die killing it manually
	[ ! -e "$PIDFILE" ] && return
	if running ; then
		kill -15 $pid
	# Is it really dead?
		sleep "$DIETIME"s
		if running ; then
			kill -9 $pid
			sleep "$DIETIME"s
			if running ; then
				echo "Cannot kill $NAME (pid=$pid)!"
				exit 1
			fi
		fi
	fi
	rm -f $PIDFILE
}


case "$1" in
  start)
	log_daemon_msg "Starting $DESC " "$NAME"
        # Check if it's running first
        if running ;  then
            log_progress_msg "apparently already running"
            log_end_msg 0
            exit 0
        fi
        if start_server && running ;  then
            # It's ok, the server started and is running
            log_end_msg 0
        else
            # Either we could not start it or it is not running
            # after we did
            # NOTE: Some servers might die some time after they start,
            # this code does not try to detect this and might give
            # a false positive (use 'status' for that)
            log_end_msg 1
        fi
	;;
  stop)
        log_daemon_msg "Stopping $DESC" "$NAME"
        if running ; then
            # Only stop the server if we see it running
            stop_server
            log_end_msg $?
        else
            # If it's not running don't do anything
            log_progress_msg "apparently not running"
            log_end_msg 0
            exit 0
        fi
        ;;
  force-stop)
        # First try to stop gracefully the program
        $0 stop
        if running; then
            # If it's still running try to kill it more forcefully
            log_daemon_msg "Stopping (force) $DESC" "$NAME"
            force_stop
            log_end_msg $?
        fi
	;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        stop_server
        # Wait some sensible amount, some server need this
        [ -n "$DIETIME" ] && sleep $DIETIME
        start_server
        running
        log_end_msg $?
	;;
  status)

        log_daemon_msg "Checking status of $DESC" "$NAME"
        if running ;  then
            log_progress_msg "running"
            log_end_msg 0
        else
            log_progress_msg "apparently not running"
            log_end_msg 1
            exit 1
        fi
        ;;
  reload)
        log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon"
        log_warning_msg "cannot re-read the config file (use restart)."
        ;;

  *)
	N=/etc/init.d/$NAME
	echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
	exit 1
	;;
esac

exit 0

Save the file!

Autostarting

Almost ready, Now, In /etc/default/darkice check that you have

RUN=yes

Then restart the service

systemctl daemon-reload

Add default user nobody to the audio group (in my case, to work with ALSA)

adduser nobody audio

Fix start sequence so that Darkice is one of the last services to load

update-rc.d -f darkice remove
update-rc.d darkice defaults 99

Not directly related to the init script, but note that darkice is being run as nobody:nobody. This user can’t set the realtime scheduling priority requests in darkice’s configuration file, so we give the binary that capability:

sudo setcap cap_sys_nice=+ep `which darkice`

Reboot and connect to your USB turntable

That is it, connect your streaming client up to (http://vinyl/listen.mp3) and put on a record, you should now be able to enjoy the tunes from the deck on your computer.

Sonos / Tunein

On Sonos, add your streaming turntable URL (http://vinyl/listen.mp3) by Using the Sonos App for iOS or Android:

  • From the Browse tab, select Radio by TuneIn.
  • Tap My Radio Stations.
  • Tap the three dots in the top right and tap Add New Radio Station.
  • Enter the Streaming URL and Station Name and tap OK.

Pi Musicbox

On Pi Musicbox, add the URL to your /boot/config/radiostations.js file or use the GUI.

Or switch to Volumio.

Acknowledgements

Data in this post is a collection from 3 sources, and combined into a single unified flow, tested on a Pi3, running Buster 2021-02

Comments

01. About Author

Damian Flynn

I define myself as an evangelist; an entrepreneur & author with an ideology rooted in business insights, technology exploration, pattern analysis and high energy. I envision, theorize and develop system architecture and strategic business platforms, soaked in storytelling and innovative technology.

Find on :

05. Categories

Do you want to learn more ?

© / 2020 / All rights reserved.
Get in Touch
Close