Use Raspberry Pi as MP3 FM transmitter

I hate to hassle around with self burned CDs in the era of giantic music collections and tiny USB sticks.
Unfortunately in my car I only have a CD player with FM radio.

Get started

You only need a 20 cm blank wire, a installation of PIFM and some of avconv black magic to workaround this car radio constraint and air your music to the radio.
A power supply is also necessary, if you own a micro USB cigarette lighter adaptor, you are lucky.
You can also use batteries like described here. (I know it is no the best example in this case :-)
For this little project, i used the raspbian image with no overclocking. See my configuration:

root@raspberrypi /opt/pifm/bin # cat /etc/debian_version
7.1
root@raspberrypi /opt/pifm/bin # uname -a
Linux raspberrypi 3.6.11+ #474 PREEMPT Thu Jun 13 17:14:42 BST 2013 armv6l GNU/Linux

Install antenna cable and the pifm software package

The antenna cable is connected to the pin like in the images:

  • 20130829_224402
  • 20130830_162341

Simple Image Gallery Extended

Download and install pifm by following this guide.
I unpacked the Pifm.tar.gz to /opt/pifm/bin
Grab the oldest FM radio and make sure you can hear the "helloworld" song (included in the archive).
You must respect the local laws, so make sure you are allowed to send in the frequency you use. :-)
Lets say 98.3 is an allowed frequency in this example.

root@raspberrypi /opt/pifm/bin # ls -l
-rwxr-xr-x 1 root root   16452 Dec 18  2012 pifm*
-rw-r--r-- 1 root root    9167 Dec 18  2012 pifm.c
-rw-r--r-- 1 root root     123 Dec  9  2012 PiFm.py
-rwxr-xr-x 1 root root 6174044 Aug 29 20:33 sound.wav*

root@raspberrypi /opt/pifm/bin # ./pifm sound.wav 98.3

[now you should hear the music playing]

After this works we face one more problem:
The pifm program is limited to exactly one file format:
WAV 16 bit mono 22050Hz
Here we bring in some scripting and much manual reading... :-)

How to air MP3 files with the PiFM radio transmitter on the Raspberry Pi

Here is a listing of the codec details of the sound.wav that is shipped with the archive. Let's use that file as our proofed reference.

root@raspberrypi /opt/pifm/bin # avconv -i sound.wav
    ...
    Stream #0.0: Audio: pcm_s16le, 22050 Hz, 1 channels, s16, 352 kb/s
    ...

Awww, I do not want to convert all files I want to play into mono wav format!
Those wav's are way larger than MP3s and the conversion process takes time and patience.
The smoothest way to work around this limitation is transforming the files "on the fly".
For this purpose, the powerful tool libav is suitable. (A fork of the popular, but end-of-life ffmpeg project)
Let's install it:

root@raspberrypi ~ # apt-get install libav-tools

The program that will transcode the mp3s into the correct format is 'avconv'.
In the very good manual I found out how to convert them:

root@raspberrypi ~ # avconv -i '/opt/music/Smooth Criminal.mp3' -ac 1 -ar 22050 -b 352k -f /tmp/smooth.wav
root@raspberrypi ~ # /opt/pifm/bin/pifm /tmp/smooth.wav 98.3

[now you should hear the music playing]

That should proof the conversion works basically. Now the "on the fly" part:
We tell the avconv program to write the result to stdout instead of a file.
That output is redirected to the stdin channel of the pifm program that is told to read from its stdin instead of a file.
Words, words, it is simpler when you read the command below. :-)

root@raspberrypi ~ # avconv -i '/opt/music/Smooth Criminal.mp3' -ac 1 -ar 22050 -b 352k -f wav - | /opt/pifm/bin/pifm - 98.3

[now you should hear the music playing]

Automate the whole thing

I wrote a script that plays a directories files.
Just make sure the script is started on boot.
Then we are finished, have fun with your pirate radio sender box!

#!/bin/bash

MUSIC_ROOT="/opt/music"
PIFM_BINARY="/opt/pifm/bin/pifm"
PIFM_FREQUENCY=98.3
LOG_ROOT="/opt/pifm/logs"
SHUFFLE="true" # true | false
WHITELIST="3gp|aac|flac|m4a|m4p|mmf|mp3|ogg|vox|wav|wma"

#####################

LOG="$LOG_ROOT/pifm.log"
mkdir -p "$LOG_ROOT"

TEMP_FILES_PREFIX='pifm.radio.tempfile'
TEMP_FILES_PATTERN="$TEMP_FILES_PREFIX.XXXXX"

{
    echo; echo -n "script $0 starting at"; date
    echo "MUSIC_ROOT: $MUSIC_ROOT"

    iteration=0
    while [ 1 ] # run forever...
    do
        iteration=$(( $iteration + 1 ))
        echo -n "start with iteration $iteration of playing all files in $MUSIC_ROOT at "; date
        rm -vf "/tmp/$TEMP_FILES_PREFIX."*

        # Collecting the songs in the specified dir

        songListFile="$( mktemp "$TEMP_FILES_PATTERN" )"

        find "$MUSIC_ROOT" -type f -follow \
        | grep -iE ".*\.($WHITELIST)$" \
        | sort \
        > "$songListFile"

        songCount="$( wc -l "$songListFile" | grep -Eo '^[0-9]*' )"

        if [ "x" = "x$songCount" ]; then echo "FATAL: no songs could be found in $MUSIC_ROOT"; exit 2; fi
        if [ $songCount -lt 1 ];    then echo "FATAL: no songs could be found in $MUSIC_ROOT"; exit 2; fi

        # Generate a playlist from the results

        playlist="$( mktemp "$TEMP_FILES_PATTERN" )"
        if [ $SHUFFLE = "true" ]; then 
            # prefix each line with random number, sort numerically and cut of leading number ;-)        
            cat "$songListFile" \
            | while read song; do echo "${RANDOM} $song"; done \
            | sort -n \
            | cut -d " " -f 2- \
            | while read song; do echo "${RANDOM} $song"; done \
            | sort -n \
            | cut -d " " -f 2- \
            > "$playlist"
        else
            cp "$songListFile" "$playlist"
        fi

        # Play each song from the playlist

        echo "will now air $songCount songs of $playlist on frequency $PIFM_FREQUENCY, enjoy!"
        cat "$playlist" \
        | while read song
        do
            # simple version: take 1st audio channel:
            #
            # command="avconv -v fatal -i '$song' -ac 1 -ar 22050 -b 352k -f wav - | '$PIFM_BINARY' - $PIFM_FREQUENCY"

            # extended version: merge audio channels:
            #
            # merge the channesl of the song to one mono channel, write to stdout:
            #     sox '$song' -t wav - channels 1
            #
            # read mono audio from stdin, convert into pifm format and write to stdout:
            #     avconv -v fatal -i pipe:0 -ac 1 -ar 22050 -b 352k -f wav -
            #
            # read compatible audio data from stdin and play with pifm at specified frequency:
            #     '$PIFM_BINARY' - $PIFM_FREQUENCY 
            command="sox '$song' -t mp3 - channels 1 | avconv -v fatal -i pipe:0 -ac 1 -ar 22050 -b 352k -f wav - | '$PIFM_BINARY' - $PIFM_FREQUENCY"



            echo "$command # $( date )"
            bash -c "$command"

        done # with playlist
        echo -n "done with iteration $iteration at "; date

    done # with endless loop :-)

} 2>&1 | tee -a "$LOG"

TODO: use pins to create a "skip" button