Wednesday, January 2, 2013

Respberry Pi with Shairport and USB Audio

Foreword

In previous article - AirTunes with Raspberry Pi, I've mentioned a way to use Raspberry Pi with Shairport for supporting AirTunes. Although there're quite a few articles on the Internet providing the guide to install Shairport on RPi, but I couldn't find a good doc mentioning how to use Shairport on Rpi with USB Audio. In this article, I'll try to explain how to intall Shairport and redirect the output to USB Audio.

Install Raspbian "Wheezy"

If you got a RPi in hand, the first thing to do is to download an OS image, and then load it on to the SD card. 

The official Raspbian "Wheezy" image is based on Debian, you can download it from here. You can choose any other OS package works on RPi, but it is better to star from the official OS package if you are new to RPi or even new to Linux based OS.

There are a few ways to make the SD card, since I'm using Mac OS X, I'll only explain how to write the image to an SD card by "dd" UNIX based utility.

Firstly, we need to open a terminal window, you can find a terminal application in /Applications/Utilities/Terminal. Since writing a image to SD card requires "root" permission, you'll need to "su"as root.
mac:~ user$ sudo -s
Password:
bash-3.2#
Insert the SD card in to a Mac. Since the Mac OS X will automatically "mount" the SD card to the system, and we can NOT write the image to a "mounted" disk, we have to "umount" it before writing the OS image to it.
bash-3.2# mount
/dev/disk0s2 on / (hfs, local, journaled)
devfs on /dev (devfs, local, nobrowse)
/dev/disk3s1 on /Volumes/NO NAME (msdos, local, nodev, nosuid, noowners)
bash-3.2#
As you can see, /dev/disk3 is the inserted SD card, and its first partition /dev/disk3s1 is mounted by Mac OS X automatically. Now we need to umount it.
bash-3.2# umount /dev/disk3s1
bash-3.2#
Also, we need to decompress the downloaded image:

bash-3.2# unzip 2012-12-16-wheezy-raspbian.zip
Archive: 2012-12-16-wheezy-raspbian.zip
inflating: 2012-12-16-wheezy-raspbian.img
bash-3.2#
Now, we are ready to write the image to SD card:
bash-3.2# dd if=2012-12-16-wheezy-raspbian.img of=/dev/disk3
It might take 20 minutes to complete, depends on the write speed of your SD card, so get a coffee and wait until it completes.

Once done, you can eject the SD card from your Mac and insert it on RPi, then plug in the power cord for booting up, you can then log in with user "pi" and password "raspberry".

Install Shairport

The following steps for installing the Shairport are mostly taken from here. However, we need to skip a few steps since we are going to use the USB Audio instead of the built-in HDMI or sound card analogue output.

After login, install the "git" utility

# sudo apt-get install git
Then install all other dependencies,
# sudo apt-get install build-essential libssl-dev libcrypt-openssl-rsa-perl libao-dev libio-socket-inet6-perl libwww-perl avahi-utils pkg-config
# sudo aptitude install libmodule-build-perl
# sudo git clone https://github.com/njh/perl-net-sdp.git perl-net-sdp
# cd perl-net-sdp
# perl Build.PL
#./Build
#./Build test
# sudo ./Build install
Get Shairport, then compile and install it
# Sudo git clone https://github.com/hendrikw82/shairport.git shairport
# cd shairport
# sudo make install

We'll need the Shairport running at RPi boot up,

# sudo cp shairport.init.sample /etc/init.d/shairport
# sudo insserv shairport

 Identify USB Audio Device

The audio devices on Linux is based on ALSA (Advanced Linux Sound Architecture), before redirecting the audio output through the USB audio device, we need to verify if our USB audio device can be recognized by ALSA.

pi@raspberrypi:~$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
Subdevices: 8/8
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
Subdevice #4: subdevice #4
Subdevice #5: subdevice #5
Subdevice #6: subdevice #6
Subdevice #7: subdevice #7
card 1: Audio [PureAudio  USB HD Audio], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
pi@raspberrypi:~$
In above, the built-in sound card (BCM2835) is recognized as card 0/device0, and my PureAudio DDC 192 is recognized as card1/device0.

Before we redirect the output of Shairport, we need to make sure our USB audio device is not muted by the ALSA by default. If so, it must be unmuted first.
pi@raspberrypi:~$ sudo alsamixer
By pressing F6, we selected the sound card 1, i. e. our USB Audio device.


As we can see, there are two sub-devices under card 1, and one of these sub-devices is muted. (Pay attention to the "MM"). By using the UP/DOWN/LEFT/RIGHT keys, we can move the cursor to the sub-device, and by pressing "M" key, we toggled it to "unmute" state. ("MM" will become "00").


Once done, press ESC to exit and save the settings.

Redirect The Output of Shairport 

In previous sections, while we installing the Shairport, we have copied a sample startup script in to /etc/init.d, so that the Shairport will be started at boot time. However, since our goal is to play the audio via the USB audio device instead of the built-in sound card, we will need to modify the startup script to redirect Shairport's output to our USB audio device. You'll need a Text-Editor to complete the job. either vi, nano or joe will do the job. If you are not familiar with vi, you will need to install nano or joe. Here's an example for installing joe and edit the shairport startup script by joe.

pi@raspberrypi:~$ sudo apt-get install joe

Reading package lists... Done
Building dependency tree

Reading state information... Done

joe is already the newest version.

The following packages were automatically installed and are no longer required:
gstreamer0.10-pulseaudio libaudiofile1 libavformat53 libfaad2 libfftw3-3 libmms0 libmpcdec6 libmpdclient2 libshout3 libsystemd-daemon0
libwavpack1 libwebrtc-audio-processing-0 pulseaudio-utils rtkit

Use 'apt-get autoremove' to remove them.
0 upgraded, 0 newly installed, 0 to remove and 30 not upgraded.

pi@raspberrypi:~$

pi@raspberrypi:~$ sudo joe /etc/init.d/shairport

Here's an example of modified startup script.
#!/bin/bash
#
# This starts and stops shairport
#
### BEGIN INIT INFO
# Provides: shairport
# Required-Start: $network
# Required-Stop:
# Short-Description: shairport - Airtunes emulator!
# Description: Airtunes emulator!
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO


# Source function library.

. /lib/lsb/init-functions

SPEAKER="MyShairport"
NAME=ShairPort
AODEV="plughw:1,0"
DAEMON="/usr/local/bin/shairport.pl"
PIDFILE=/var/run/$NAME.pid

#DAEMON_ARGS="-w $PIDFILE -a $NAME"
DAEMON_ARGS="-w $PIDFILE -a $SPEAKER --ao_devicename=$AODEV"

[ -x $binary ] || exit 0

RETVAL=0

start() {
    echo -n "Starting shairport: "
    start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
                      --exec "$DAEMON" -b --oknodo -- $DAEMON_ARGS
    # Unmoute alsamixer
    amixer set -c 1 "PureAudio  Clock Selector" 127 unmute
    log_end_msg $?
At first, we can defined a "speaker name" for Shairport, so that you can identify it in iTunes.
SPEAKER=MyShairport
Secondly, we need to define an audio output device for redirecting the Shairport's output to the device. Since our USB audio device was identify as card 1/device 0 in previous section, we defined the AODEV as following:
AODEV="plughw:1,0"
Then we need to comment the original DEMON_ARGS by putting a "#" in front of the line, and recreated a new line to replace it.
#DAEMON_ARGS="-w $PIDFILE -a $NAME"
DAEMON_ARGS="-w $PIDFILE -a $SPEAKER --ao_devicename=$AODEV"

In the "start()" section, we need to add a line to "unmute" our USB audio device automatically at boot time instead of using alsamixer to toggle the setting everytime.
# Unmute alsamixer
amixer set -c 1 "PureAudio  Clock Selector" 127 unmute
Once completed, save the startup script, and then reboot the RPi. You should be able to airplay the music in your iTunes via the "MyShairport".
pi@raspberrypi:~/shairport$ sudo reboot

Troubleshooting

If you can see the "MyShairport" in your iTune, but cannot hear anything, try to log on RPi, and use alsamixer to check if it is muted again.

Caveat

According to the hardware architecture of Raspberry Pi, the network interface is connected to the processor via USB as well. Hence the efficiency of USB host driver will be critical for using USB audio device. Unfortunately, the USB host driver of RPi is not so efficient, even buggy. Don't be surprise if you found some unexpected clicks or pops while playing audio files through Shairport.

I've tested playing a WAV file on RPi locally with USB audio device, even without any network traffic, I'll still get some clicks and pops.

However, I do have successfully airplayed audio on RPi without any clicks/pops by using another USB audio device. The difference in between is the PCM output format of USB, the one without clicks/pops transferred the PCM to USB audio device using 24 bit on 3 bytes (24_3LE) for each audio sample, and the one with clicks/pops used 24 bit on 4 bytes (32_LE). It seems that by increasing 25% precent in the amount of data on the USB bus, it is more easier to get the clicks/pops. Hope the RPi foundation and BroadCom will come out with a solution for the USB host driver soon, or else, we may need to look for another solution.

On the other hand, with RPi's simplified hardware architecture with Async USB audio device, it sounds really beautiful and accurate. To me, it might be a prefect solution if RPi can get the USB issues resolved. I'll pay a little bit more patient for RPi for awhile unless I found another neat solution.