I was very happy to find two guides to setting up a Raspberry Pi to stream music from a turntable with a USB output, so we could listen to it on our existing Sonos speakers and via a network-connected receiver in a different room.

I adapted the two guides and learned a few things along the way – this is mostly a record for my own reference, but perhaps it will be of help to someone else too.

The changes from coreyk’s and basdp’s guides are roughly:

  • Used Raspbian Stretch (no changes needed)
  • Use stock darkice binaries from the repository
  • darkice init script tweaks
  • Stream from port 80 so you don’t have to specify a port
  • Power control from a mobile via a URL
  1. Stock darkice
  2. darkice init script tweaks
  3. Default port
  4. Reboot and shutdown from a mobile

Stock darkice

coreyk compiles darkice from source, to add AAC+ support. I don’t need this, as on a local network the suggestion of doing fixed 320kbps MP3 encoding is fine.

basdp provides their own .deb. This is fragile (what versions of raspbian will it work with?) and there’s no way to tell if it’s trustworthy.

I had no issues with the stock darkice. This meant the only packages I needed to install were darkice and icecast2 (and vim!).

darkice init script tweaks

I followed coreyk’s guide, but instead of deleting the pidfile with rm, you can pass --remove-pidfile to start-stop-daemon when stopping.

To get logging output from darkice, I changed the start invocation to make use of --no-close:

LOGFILE="$LOGDIR/$NAME.log"
...
start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
            --background --chuid $USER:$GROUP --no-close \
	    --exec $DAEMON -- $DAEMON_OPTS >> $LOGFILE 2>&1

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`

Default port

Both guides suggest setting up icecast to listen on port 8000. This is fine, but makes the URL slightly ugly.

With port 80, you can stream from http://vinyl.local/listen.

The port can easily be changed in /etc/darkice.cfg and /etc/icecast2/icecast.xml, the trick is to give icecast the capability to bind to port 80:

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

Reboot and shutdown from a mobile

To provide a clean shutdown option, I wrote this little script to listen for web requests. A shortcut to the URL alongside the Sonos app on my phone’s launcher then provides a convenient way to cleanly shut it down. I don’t know what the risk of SD card corruption is from hard power-offs, but this eases my mind.

It’s called from /etc/rc.local so it should always be running when the Pi is. This also means it runs as root - not generally a good idea for a web server, to say the least, but in this case it’s quite convenient as that gives it permission to shutdown and reboot.

#!/usr/bin/env python3

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib import parse
import subprocess

# HTTPRequestHandler class
class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed_path = parse.urlparse(self.path).path
        if parsed_path in ['/reboot','/restart']:
            code = 200
            message = "Rebooting..."
            subprocess.Popen(["shutdown", "-r", "now"]) # will run concurrently
        elif parsed_path in ['/halt','/shutdown','/stop']:
            code = 200
            message = "Shutting down..."
            subprocess.Popen(["shutdown", "-h", "now"]) # will run concurrently
        else:
            code = 404
            message = "Not found. Available paths: /reboot, /halt\n"

        self.send_response(code)
        self.send_header('Content-type','text/html')
        self.end_headers()

        # Send response to client as utf8
        self.wfile.write(bytes(message, "utf8"))
        return

def run():
    # bind to all addresses, unprivileged port
    server_address = ('', 8000)
    httpd = HTTPServer(server_address, handler)
    print('running server...')
    httpd.serve_forever()

run()