AirPi: DIY Airplay Speakers using Shairport and a Raspberry Pi

August 28, 2012

We have speakers in all the ground floor rooms of our house, all driven from the same amp. It’s neat but controlling the input requires going back to the amp.

Surrounded by iDevices too and with apps like iPlayer, Spotify and home share on iTunes, being able to throw audio to the speaker system had to be done. Que Airplay, however, this requires a nice Airplay amp or getting an AirPort. I then found out about Shairport, a program that emulates an AirPort’s Airplay function. With a Raspberry Pi kicking around, I had just found its new job.

Quite a lot has changed in the year since I did this and rather than try and add more updates to this, I have run through the process again and created a new tutorial.

Setting Up Arch ARM

I opted for the Arch Pi distribution for this, simply because it is my Linux of the moment and with no window manager, is perfect for a standalone device. The first thing you’re going to want to do is a system update with pacman (you’ll probably need to update pacman on first run so need to do this twice).

pacman -Syu

Next you’ll need to install the tools required to compile in Arch.

pacman -S kernel26-headers file base-devel abs

Then git to clone the Shairport repo.

pacman -S git

Shairport has a number of dependencies so we’ll install them and there dependencies too.

pacman -S avahi libao openssl perl-crypt-openssl-rsa perl-io-socket-inet6 perl-libwww

Finally, alsa is required to get sound output in Arch on the RPi. Install this and then load the sound driver using modprobe.

pacman -S alsa-utils alsa-oss
modprobe snd-bcm2835

Alsa mutes the channels by default so open the mixer and raise the volume to 0dB gain. Test the output using speaker-test.

alsamixer
speaker-test -c 2

I’m using the 3.5mm jack as an audio output and at this stage I failed to get audio. I realised that with the HDMI plugged in, audio was going through that and not through the jack (it doesn’t seem to do both at once). You need to disconnect the HDMI and reboot the RPi. If you’re connected via monitor and want 3.5mm jack, there is no option but to continue via ssh or use the phono. I was doing it all via ssh so it didn’t really matter. If you do reboot, don’t forget to reload drivers using modprobe before testing again.

update: you can change output with using amixer cset numid=3 1tomsolari.id.au ]

If all is good, save the alsa levels.

alsactl store

And set the sound modules and new daemons to load at startup by editing /etc/rc.conf

vi /etc/rc.conf
MODULES=(.. snd-bcm2835 ..)
DAEMONS=(.. dbus avahi-daemon alsa ..)

Probably a good idea to reboot at this stage.

shutdown -r now

Make Shairport

Make a directory called Shairport in the home folder.

mkdir shairport

Now clone the repo, cd into it and make.

git clone https://github.com/albertz/shairport.git shairport
cd shairport
make

All being well, Shairport should have built and you can now run it with the name ‘AirPi’.

./shairport.pl -a AirPi

Create daemon

18/12/12 – This may not work now due to Arch builds now using systemd. If it doesn’t, this comment thread has a solution.
Install the new build

make install

Shairport includes a sample init file but it is not designed for Arch. Arch includes a template for creating new daemons in /usr/share/pacman/rc-script.proto, first copy to /etc/rc.d

cp /usr/share/pacman/rc-script.proto /etc/rc.d/shairport

Now edit the file using vi as mine below

#!/bin/bash
daemon=shairport
daemon_name=shairport.pl

. /etc/rc.conf
. /etc/rc.d/functions

get_pid() {
    pidof -o %PPID $daemon_name
}

case "$1" in
    start)
        stat_busy "Starting $daemon"

        PID=$(get_pid)
        if [[ -z $PID ]]; then
            [[ -f /var/run/$daemon_name.pid ]] &&
                rm -f /var/run/$daemon_name.pid
        # RUN
        $daemon_name -d -a AirPi
        #
        if [[ $? -gt 0 ]]; then
            stat_fail
            exit 1
        else
            echo $(get_pid) > /var/run/$daemon_name.pid
            add_daemon $daemon_name
            stat_done
        fi
        else
            stat_fail
            exit 1
        fi
        ;;

    stop)
        stat_busy "Stopping $daemon_name daemon"
        PID=$(get_pid)
        # KILL
        [[ -n $PID ]] && kill $PID &> /dev/null
        #
        if [[ $? -gt 0 ]]; then
            stat_fail
            exit 1
        else
            rm -f /var/run/$daemon_name.pid &> /dev/null
            rm_daemon $daemon_name
            stat_done
        fi
        ;;

    restart)
        $0 stop
        sleep 3
        $0 start
        ;;

    status)
        stat_busy "Checking $daemon_name status";
        ck_status $daemon_name
        ;;

    *)
        echo "usage: $0 {start|stop|restart|status}"
esac

exit 0

# vim:set ts=2 sw=2 et:

Add shairport to the list of daemons in etc/rc.conf and you’re all good to go.

UPDATE 2:

I’ve since found a command to redistribute the Pi’s memory, providing less dropped audio (it doesn’t happen much anyway). By default, a certain amount is allocated to the GPU, since this is headless, we can remove that.

cd /boot
mv start.elf orig-start.elf
cp arm224_start.elf start.elf

UPDATE 3:

Shairport has been updated to support iOS and with it now depends on perl-net-sdp. This can be installed from the AUR using these instructions