layout: page
title: "pi-audio"
date: 2014-03-24 21:29
comments: true
sharing: true
footer: true

Zone Audio system using Raspberry Pi

I want to be able to stream audio from any mobile device (laptop, iDevice, Android-, guest's phones) to any romo in the house. I also want to be able to take the podcast I'm listening to upstairs and "move" it downstairs, or to the other room as I move around.

Hardware Setup

Software setup.

I started with a vanilla Raspbian install on the Pi.

There are several methods for transmitting audio over a network:

I want to support as many as possible, reaching any or multiple endpoints, and without contention (mixing if multiple devices want to stream to the same speakers.)

Abandoned first idea: install PulseAudio:

Nothing worked. Playing with ALSA made Pulse hang and Pulse tried to send things back to ALSA and it was a 100% CPU util and zero. Struggled, mightily, until I uninstalled PulseAudio and magically ALSA started working. See a page of frustrated notes

First: get ALSA straightened.

(Note, this page, interestingly, seems to reccommend a OSS v4. I might come back to that when I have a low latency application.

List ALSA playback devices:

pi@raspberrypi:~/blog/source/pi-audio$ aplay --list-devices
**** List of PLAYBACK Hardware Devices ****
card 0: system_1 [iMic USB audio system], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: system [iMic USB audio system], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: 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

Not sure what all the subdevices for the bcm2835 are. Analong, HDMO, in, out?

It's also somewhat disturbing that the cards are numbered with the USB devices first Udev is supposed to stop that from happening. How can I refer to a USB device stably so that living room stays living room through restarts? Ideally refer by device serial number?

How to make ALSA refer to USB devices stably.

USB devices, in general, will not get assigned to the same places between restarts, so I need some way to make sure that if I play to the bedroom I'll get the bedroom.

The Alsa-project.org wiki had a page showing how to assign device names based on the path to the particular port the device is plugged in to.

Unplugging and replugging for some reason does not show up on udevadm monitor. So I rebooted with the audio cards unplugged from USB, then ran udevadm monitor This got me:

KERNEL[101.908473] change   /devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.4/1-1.3.4:1.0/sound/card1 (sound)
KERNEL[112.194239] change   /devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.3/1-1.3.3:1.0/sound/card2 (sound)

NOTE this did not work for another brand of USB audio device, even booting with things unplugged. Anohter thing I did was extract the device paths from the output of udevadmin info --export-db.

So I added these lines to /etc/udev/rules.d/39-usb-alsa.rules:

SUBSYSTEM!="sound", GOTO="my_usb_audio_end"
ACTION!="add", GOTO="my_usb_audio_end"

DEVPATH=="/devices/platform/bcm2835_AUD0.0/sound/card?", ATTR{id}="builtin"
DEVPATH=="/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.4/1-1.3.4:1.0/sound/card?", ATTR{id}="living_room"
DEVPATH=="/devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.3/1-1.3.3:1.0/sound/card?", ATTR{id}="office"

LABEL="my_usb_audio_end"

Saving that and rebooting, I find that ALSA now has names for my audio cards:

pi@raspberrypi:~/blog/source/pi-audio$ aplay --list-devices
**** List of PLAYBACK Hardware Devices ****
card 0: living_room [iMic USB audio system], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: office [iMic USB audio system], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

However, it did not manage to change the "id" on the builtin. No matter, as it is terrible and I won't be using it.

And ALSA can play using those names;

aplay -D default:living_room /usr/share/scratch/Media/Sounds/Animal/HorseGallop.wav

Writing an asound.conf

The native sample rate on these things is 48000 Hz, if I remember the spec sheet right, so let's make ALSA resample in software.

I also want some virtual devices for "everywhere," "upstairs," etc.

Here's the etc/asound.conf I wrote (note it gets more complicated down below with mixing:

pcm.raw_living_room {
    type hw
    card living_room
    rate 48000
}

pcm.raw_office {
    type hw
    card office
    rate 48000
}

pcm.living_room {
    type plug
    slave { pcm "raw_living_room" }
}

ctl.living_room {
    type hw
    card "living_room"
}

pcm.office {
    type plug
    slave { pcm "raw_office" }
}

ctl.office {
    type hw
    card "office"
}

pcm.!default {
    type plug
    slave { pcm "raw_living_room" }
}

ctl.!default {
    type hw
    card "living_room"
}

pcm.everywhere {
    type plug;
    slave.pcm {
        type route;
        slave.pcm {
           type multi;
           slaves.a.pcm raw_living_room;
           slaves.a.channels 2;
           slaves.b.pcm raw_office;
           slaves.b.channels 2;
           bindings.0.slave a;
           bindings.0.channel 0;
           bindings.1.slave a;
           bindings.1.channel 1;
           bindings.2.slave b;
           bindings.2.channel 0;
           bindings.3.slave b;
           bindings.3.channel 1;
        }
        ttable.0.0 1;
        ttable.1.1 1;
        ttable.0.2 1;
        ttable.1.3 1;
    }
}

pcm.pulse { type pulse }
ctl.pulse { type pulse }

I found I could aplay -D everywhere and get sound out of both speakers (with some popping).

Note that you may often get warnings on the ALSA site about ganging audio cards together, that their clocks might drift. The nice thing about USB audio devices is that they (or at least some of them, like the original iMics) derive their 48000Hz clock off of the 1Khz frame clock on the USB bus. So two devices on the same bus will stay in relative synch indefinitely. As long as they are fed properly by the audio drivers...

Upgrade firmware

I ran rpi-update since audio and USB concention have been worked on and this Pi is quite old. No change that I saw (yet).

Next step: Don't add PulseAudio back

Actually, no. The only thing that Pulseaudio ever did was prevent sound from being played (even though ALSA, already worked, what), and hog the CPU. So, make sure to uninstall pulseaudio. See a second page of frustrated notes.

Make ALSA do mixing

After uninstalling PulseAudio, ALSA magically started working again.

So I decided to set up software mixing and multi-clients in ALSA. See this page.

Eventually my /etc/asound.conf looks like this. Note the numeric paramters, whcih I copied from somewhere without really experimenting, but they seemed to take care of popping.

And it allows mixing of multiple streams from here, there, everywhere:

cd /usr/share/scratch/Media/Sounds/Vocals
mpg321 -o alsa -a everywhere Oooo-badada.mp3 & mpg321 -o alsa -a office Sing-me-a-song.mp3

Now, I get some dropouts when playing three mp3 streams at once, but that seems to be mpg321's inefficiencies.

Configure shairport

Clone the repo from github:

pi@raspberrypi:~$ git clone https://github.com/abrasive/shairport.git

Install build dependencies:

sudo apt-get install libssl-dev libavahi-client-dev libasound2-dev libao-dev libpulse-dev

Compile:

cd ~/.shairport && ./configure && make

Test:

./shairport -a "Living room" -p 5002 -- -d living_room &
./shairport -a "Office" -p 5003 -- -d office &
./shairport -a "Everywhere" -p 5004 -- -d everywhere &

That, remarkably, worked. Multiple endpoints can even play simultaneously (even endpoints that share the same physical speakers). It doesn't allow multiple connections per endpoint, though its contention behavior is better than the airport (it boots off old connections instead of letting them hog)

Then the thing is to make the shairports run at startup as daemons. I mashed together some example init.d scripts found on the web and made this which I placed at /etc/init.d/shairport. Note the script makes the daemons run with realtime priority. Then I ran:

sudo update-rc.d shairport defaults

to enable the script at startup.

Note: I think I want the daemon to run at realtime priority only when it is streaming, but not when waiting for connections? Can that be done?

Popping and clicking fix

Popping and clicking was worse with the Audio-Technica dongles than with the iMics. I found a page about a branch of the Raspberry pi kernel that fixes the USB lossage.

But this fix has already been merged and I've been using it! However, there are some tweaks. Putting

dwc_otg.fiq_split_enable=0

in /boot/cmdline.txt made crackling much worse! But adding it at the beginning of the command line had better luck -- crackle when starting a strem which settles down, a bit worse (occasional crackles and skips) when 'everywhere' -- but then the crackles get horrible after a half hour or so. Really, bright, destroy-your-tweeters electric cracks, as opposed to the popcorn glitches I get when the option is off. (Doesn't depend on whether I'm using multiple devices, either.

Another thing to try is:

dwc_otg.speed=1

which eliminated crackles, or at least slowed them down to once or twice in a half hour.. This option forces USB1.1 speeds. The disadvantage is that more than two endpoints will refuse to play because of insufficient USB bandwidth. That's what I'm sticking for for the moment.

Other options:

dwc_otg.microframe_schedule=0 made USB fail to start up at all because of failures to allocate enough periodic transfers.

See this thread and this thread and this thread for some hints and other things to try.

Nothing satisfactory yet so the next thing is COMPILE A KERNEL, from the last link.

Compile a kernel

Linux always comes to this...

I cross-compiled the kernel mentioned in one of the previous threads, and described that process on a separate page.

Short story, it didn't work. Crackles/pops just as bad, and forcing USB 1.1 speeds made the Ethernet drop packets.

Try Swapping out the DACs

Argh, ALWAYS try the simple things first. I swapped back the two Griffin iMics for the C-Media based other devices Mics and that almost eliminated glitches with fiq_split_enable=1. This is with the original kernel too (no dropping Ether packets when loaded.)

Now when I try to play simultaneously through two iMics and an ATR2USB, the ATR drifts out of sync while the iMics stay in sync.... THAT TELLS ME that they might be differing in terms of whether they operate in sync/async/adaptive mode (and this might explain pops and crackle.) How to tell which endpoints are in use. or configure the particular endpoints to use?

DLNA service

DLNA is the competing protocol to AirPlay. Actually not really competing, in that they don't do much of the same tasks at all, (AirPlay decompresses on the client and streams audio to the server; DLNA gets a media player, media library server, and remote control to work together.) But ler's add a DLNA service as well.

A recommended DLNA audio renderer is gmrender-resurrect

It downloads and builds well enough:

git clone https://github.com/hzeller/gmrender-resurrect.git
sudo apt-get install libupnp-dev gstreamer0.10-alsa gstreamer0.10-plugins-base libgstreamer-plugins-base0.10 libgstreamer-plugins-base0.10-dev libgstreamer0.10-dev
./autoconf
./configure
make

Let's try and find a DLNA player to test it out.

Then set it up with an init script...

TODO