BBC Radio on the Raspberry Pi (v2)

Posted in Posted on 2014-05-18 19:33

BBC Radio on the Raspberry Pi (v2)

This is an update to my recent post on this topic.

This page shows how to create a simple radio command to play and stop different BBC radio stations on a Raspberry Pi. Once set up, you can just type e.g. radio BBC4 to get your favourite station playing. This is useful for various reasons, for instance if you have a room with just an amplifier and speakers in then, with a Raspberry Pi, you can listen to the radio (and with other software your music collection). You can also listen to BBC 6 Music which you cannot get on FM.

The scripts described on this page are available to download from github as part of a larger project which I’ll be blogging on soon.

Getting BBC radio streaming from a Raspberry Pi is something I’ve been wanting to do since I started playing around with them. I’ve seen various impressive solutions involving re-purposing old portable radios but I never got the hang of what needed doing in software.

This tutorial is based on a Raspberry Pi with the latest version of Raspbian installed (2014-01-07), updated using rpi-update. The OS also has some other packages installed to enable it to be a UPnP renderer. This is all documented in a previous post.

Updating the stream addresses

Part of the complication with BBC radio stations is that the URLs required to stream from change periodically. First of all therefore we need a script to find the URLs and cache them. The “playlist” URLs do not change (very often), but their contents does. Lets look at one using the curl command:

$ curl http://open.live.bbc.co.uk/mediaselector/5/select/mediaset/http-icy-aac-lc-a/vpid/bbc_radio_one/supplier/ll_icy2/format/pls.pls
NumberOfEntries=1

File1=http://bbcmedia.ic.llnwd.net/stream/bbcmedia_lc1_radio1_q?s=1414757382&e=1414771782&h=9533b45c836fee7271d17f9a4bdd7eff
Title1=No Title
Length1=-1

The actual URL for streaming is the one following File1=. What we need to do is download all the playlists we want and extract each of these URLs. To do this, create a script called bbc_radio_update, e.g. using sudo vi /usr/local/bin/bbc_radio_update:

raspberry-radio/scripts/bbc_radio_update (Source)

#!/bin/bash

# This script is based on one found in what is assumed to be the public domain.

set -e
playlist=/var/local/bbc_radio/urls
rm -f $playlist

declare -A radios

radios["BBC1"]="http://www.radiofeeds.co.uk/bbcradio1.pls"
radios["BBC1x"]="http://www.radiofeeds.co.uk/bbc1xtra.pls"
radios["BBC2"]="http://www.radiofeeds.co.uk/bbcradio2.pls"
radios["BBC3"]="http://www.radiofeeds.co.uk/bbcradio3.pls"
radios["BBC4"]="http://www.radiofeeds.co.uk/bbcradio4fm.pls"
radios["BBC4x"]="http://www.radiofeeds.co.uk/bbcradio4extra.pls"
radios["BBC5l"]="http://www.radiofeeds.co.uk/bbc5live.pls"
radios["BBC5lx"]="http://www.radiofeeds.co.uk/bbc5livesportsextra.pls"
radios["BBC6"]="http://www.radiofeeds.co.uk/bbc6music.pls"

for k in "${!radios[@]}"
do
        pls=${radios[$k]}
        curl -s $pls | grep File1 | sed "s/File1=/$k, /" >> "$playlist"
done

We then need to make the script executable (by all users), create a directory for the output URLs to be placed (that all users can write to) and run the script:

$ sudo chmod 755 /usr/local/bin/bbc_radio_update
$ sudo mkdir /var/local/bbc_radio
$ sudo chmod 777 /var/local/bbc_radio/
$ bbc_radio_update

It is normally not considered very secure to have a directory such as /var/local/bbc_radio which all users can write to but it is the simplest way of making it so that all users can run the command and it really doesn’t matter for a Raspberry Pi in the home.

To look at the output, just cat the file (note the contents of this file changes: yours will not be the same):

$ cat /var/local/bbc_radio/urls
BBC4, http://bbcmedia.ic.llnwd.net/stream/bbcmedia_lc1_radio4_p?s=1399723465&e=1399737865&h=2cebe3f0ad99df0e6f0f4e56ca4d7736
BBC6, http://bbcmedia.ic.llnwd.net/stream/bbcmedia_lc1_6music_p?s=1399723579&e=1399737979&h=b85295564b4afb786f533d75023ba57c
...etc

You can make this script run periodically by linking it to e.g. /etc/cron.hourly with the command sudo ln -s /usr/local/bin/bbc_radio_update /etc/cron.hourly/ but there will still be times when the URLs are out of date. The script to run the radio will take care of this instead.

Playing the streams

Now we have the necessary URLs we need to play them. Although I use gstreamer with gmediarender as a UPnP renderer I cannot get it to play the BBC stations so instead we use mpd and control it with mpc. Firstly we need to install them (and ignore the errors):

$ sudo apt-get install mpc mpd
...etc...
Setting up mpc (0.22-1) ...
Setting up mpd (0.16.7-2) ...
insserv: warning: script 'mathkernel' missing LSB tags and overrides
[....] Starting Music Player Daemon: mpdlisten: bind to '[::1]:6600' failed: Failed to create socket: Address family not supported by protocol (continuing anyway, because binding to '127.0.0.1:6600' succeeded)
Failed to load database: Failed to open database file "/var/lib/mpd/tag_cache": No such file or directory
. ok

At this point if we try to use mpc, mpd can’t access the ALSA output. Looking in the /var/log/mpd/mpd.log we see:

output: Failed to open "My ALSA Device" [alsa]: Failed to open ALSA device "hw:0,0": Device or resource busy

Edit /etc/mpd.conf and comment out some lines:

audio_output {
    type "alsa"
    name "My ALSA Device"
# device "hw:0,0" # optional
# format "44100:16:2" # optional
# mixer_device "default" # optional
# mixer_control "PCM" # optional
# mixer_index "0" # optional
}

The key change may be the mixer_device line (not sure). Then restart the mpd service:

$ sudo /etc/init.d/mpd restart

Making it easy with a “radio” command

Finally, we need a helpful script to make controlling mpd easy. Save this script as /usr/local/bin/radio:

raspberry-radio/scripts/radio (Source)

#!/bin/sh

# This code is copyright Stephen C Phillips (http://scphillips.com).
# It is licensed using GPL v3.

URLS=/var/local/bbc_radio/urls

# get the radio URLs if they are not there
[ ! -f $URLS ] && bbc_radio_update

case "$1" in
    "stop")
        mpc -q clear
    ;;
    "status")
        # Run the "mpc" command, remove any line starting "volume"
        # and replace any line starting with "http" with "Unknown station"
        mpc | grep -vE "^volume:" | sed "s/^http:.*/Unknown station/"
    ;;
    "stations")
        # Take the URLS file, split each line at the comma
        # and just print the first field, then sort them
        cat $URLS | cut -d',' -f1 | sort
    ;;
    "reset")
        # Stop whaever is playing and then update the URL list
        echo "Fetching station URLs..."
        mpc -q clear
        bbc_radio_update
    ;;
    *)
        # Search the URLS file for this script's argument ($1) followed
        # by anything and then a comma. Redirect it to /dev/null so the result
        # isn't shown in the console.
        grep -i "$1.*, " $URLS > /dev/null
        # Check if we found a URL in the URLs file matching the argument.
        if [ $? -eq 0 ]; then
            mpc -q clear
            # Again, search for the argument, take just the first line that matches
            # (using "head"), split the line at the comma and take the second field.
            # Add this to the playlist with "mpc add".
            mpc -q add `grep -i "$1.*, " $URLS | head -1 | cut -d',' -f2`
            mpc -q play
            sleep 2
            # Check if we just found an error (often caused by URLs being out of date).
                        # Message could be "ERROR: problems decoding" or "ERROR: Failed to decode"
            mpc | grep "ERROR: .* decod" > /dev/null
            if [ $? -eq 0 ]; then
                # If mpc reported an error then fetch the URLS and try again.
                echo "Fetching station URLs..."
                mpc -q clear
                bbc_radio_update
                mpc -q add `grep -i "$1.*, " $URLS | head -1 | cut -d',' -f2`
                mpc -q play
            fi
            # Get the status, remove the volume line, replace any URL with
            # "unknown station" and this time let it appear on the console.
            mpc | grep -vE "^volume:" | sed "s/^http:.*/Unknown station/"
        else
            echo "No such station"
        fi
    ;;
esac

This script needs some explanation. It has got more complicated that the first version I posted a week ago.

The script supports the commands stop, status, stations, reset and any other command is assumed to be the name of a radio station. The first argument to a script is put into the $1 variable automatically, so the case statement on line 10 causes different bits of script to be run depending on what command you type.

Before we get to the main script, line 9 checks to see if the URLs file exists and, if not, runs the bbc_radio_update script to fetch them.

To work out what is going on with the other commands, you can just try typing most of the commands at the command line. For instance, for the status command, try typing mpc when nothing is playing. It just prints:

$ mpc
volume: n/a repeat: off random: off single: off consume: off

The additional pipe to grep -vE "^volume: on line 18 means that everything except lines starting “volume:”” is printed: we are not interested in those lines so we filter them out. The sed command replaces any line starting with “http:” with “Unknown station” as sometimes when a radio station is playing it will print the station name and sometimes it doesn’t know it and just prints the stream URL (which is not very useful so we replace it). If you were playing BBC Radio 4 then the mpc and the radio status command would give:

$ mpc
BBC Radio 4
[playing] #1/1 0:05/0:00 (0%)
volume: n/a repeat: off random: off single: off consume: off
$ radio status
BBC Radio 4
[playing] #1/1 0:08/0:00 (0%)

The radio reset command clears the mpc queue (so stops it playing) and runs the bbc_radio_update command (this is why all users need to be able to run it). It shouldn’t normally be necessary to do this by hand though as the complicated command for playing a radio station takes care of it most of the time.

So, if you type radio bbc4 then we get to line 35 in the script where there is a grep command. That searches the URLs file (ignoring case) for e.g. “BBC4, ” and sends the output to /dev/null (so we don’t see it). It is actually searching for the script’s argument (e.g. “BBC4”) followed by anything (the “.*”) followed by a comma and space. This means that the script will work if you just type radio 4. The next line asks if “$?” is equal to zero: “$?” is the return code of the last command (the grep) and if it is zero then it means that grep found something and so the station exists in the URLs file. In this case (on line 38) we clear the mpc playlist (and suppress the status message with “-q”) then (on line 42) we get the correct URL out of the file and add it to the playlist. To do this we use grep again and pipe the output first into head -1 to get only the first matching line, then into the cut command which splits a line, setting the delimiter to a comma and choosing the second field. We play the station on line 43 (again suppressing output).

That pretty much what I had in the previous version. Sometimes this failed silently if the radio station URLs were out of date. Also, if it did work then it just told you the URL you are playing. If instead you wait a moment and then do mpc then you get the name of the radio station. This is why there is now a sleep 2 statement on line 44. It is followed by mpc | grep "ERROR: problems decoding": if the URL is out of date (or there are other issues) then you get back from mpc the text ‘ERROR: problems decoding “http://bbcmedia.ic.llnwd.net/…etc“’. We use grep to see if we have this error, testing the grep output on line 48. If we have an error then we want to do the radio reset command sequence and try playing the station again. Lines 48-53 achieve this. Finally we do the status command again.

Make it executable and try it out:

$ chmod 755 /usr/local/bin/radio
$ radio bbc4
BBC Radio 4
[playing] #1/1 0:01/0:00 (0%)
$ radio status
BBC Radio 4
[playing] #1/1 0:14/0:00 (0%)
$ radio stop

If your URLs file is out of date then you will see something like:

$ radio bbc4
Fetching station URLs...
BBC Radio 4
[playing] #1/1 0:01/0:00 (0%)

That’s all for now!

Updates:

  • 2014-05-19: made some improvements to the radio script
  • 2014-05-21: radio script bug fix
  • 2014-05-24: made radio script automatically fetch URLs the first time and added in “Unknown station” feature
  • 2014-06-29: added lots of comment to radio script, made the grep better, added links to github
  • 2014-10-31: updated the BBC playlist URLs (thanks to Daniel J Wilcox)
  • 2014-12-30: small improvement to the out-of-date URLs check

Comments

Comments powered by Disqus