Next we move to audio. Linux sound is rather poorly documented, and therefore rather hard to configure if you don’t know what you’re doing. The initial goal was to play audio from squeezelite (a software Squeezebox client), and also play audio from the TV (line-in on the sound card).
First Try: dmix and alsaloop
My first attempt was to use dmix to mix audio from the squeezelite service with the line-in on the UCA-202 sound card. Getting the squeezelite signal into dmix was relatively easy once I figured out the general structure of the ALSA configuration file. However, getting the line-in channel of the sound card to be passed through to the line-out channel was more challenging. I ended up using a program called alsaloop to send audio from an ALSA line-in device to another line-out device.
Sample rates were a problem though. For some reason the UCA-202 seems to prefer sampling the line-in at 48kHz (I didn’t look into this much; maybe I did something wrong). Therefore, 44.1kHz audio, which is common for CD audio, will not play when alsaloop is running. This is just one example of conflicting sample rates. In this type of situation, if a particular audio stream was playing, the other process (alsaloop or squeezelite) would consume 100% CPU when started, and the audio coming out of the speakers would become very choppy or stop abruptly. Through more digging I learned that I needed a resampling plugin. I tried libsamplerate, but it did not seem to work. I don’t know what was wrong with it, but the primary symptom was very high CPU usage and choppy or stopped audio—similar to no resampling at all. Then I found the speexrate plugin, which worked very well. It used varying amounts of CPU depending on the quality setting, but most importantly it worked, which was progress. To use the speexrate plugin add one of the following lines to your asound.conf:
Conceptually I realized that the mixing was unnecessary, but this setup had the advantage that any sound source (the TV connected to line-in or squeezelite) could be turned on and it’s audio signal immediately started to play through the speakers. The disadvantage is that dmix requires that all incoming audio signals be resampled to a single common sample rate.
Better solution: snd-aloop kernel module with alsaloop
The single sample rate requirement of dmix bothered me because I wanted to play audio at its native sample rate to avoid loss of quality due to resampling. Conceptually, I wanted a software source switch similar to the source switch you might find on an AV amplifier/tuner unit. At first I looked for a program to set on top of ALSA to do this. Ideally, I would like to control it from my iTouch…
After a few false starts, a bit of digging, and some inferring about how the ALSA config worked, I was able to achieve what I wanted: no resampling. This involved configuring the snd-aloop kernel module, which provides ALSA loopback devices. Each input source is routed into a loopback device. A config file is better than words do describe how this is done.
A few notes about this setup:
The loopback devices allow the connected source (squeezelite, shairport, or line-in) to operate normally without being connected to the line-out device. Without them, a playing input source would cause high CPU load and general system instability if it was not connected to the line-out device. I’m assuming this is because ALSA did not know what to do with the audio stream? The loopback devices seem to know how to discard the input signal if there is nothing connected to the output end of the loop.
I put a softvol on line-in because my TV only has line-level outputs. This is annoying because I can’t use the TV remote to control the volume. I had to come up with another solution (more on that later).
Likewise, a softvol on airpogo because ShairPort (AirPlay) volume could not be adjusted precisely at lower volume levels without it. It may be a bug in shairport? This is a hack, and definitely not ideal. It’s sort of a trim control that makes it possible to adjust the volume on the client over a subset of the full volume range. Setting the level of the softvol control to approximately 50% allows AirPlay clients to adjust the volume at comfortable to quiet listening levels, but it doesn’t give full range control to the client. This is acceptable since I’m not normally trying to blast the walls down.
If you’ve followed closely you’ll notice that we’re missing the connection between the loopback devices and the line out. Enter alsaloop. It is used like a cable to route output from a loopback device to the line_out device. Because alsaloop is a process that can be started and stopped, it provides exactly what we need to implement the input source switch. And each source can play at its native sample rate since there is only ever one source routed through the line-out device. The line-out device automatically assumes the sample rate of the input source. Caveat: this works as long as the samplerate is supported by the sound card. The UCA-202 does not support sample rates above 48KHz, so I’d need to do some resampling for 96KHz audio, but I don’t have any audio encoded at that sample rate.
I had some sound quality issues with squeezelite when I first got this set up. The symptoms were crackling and popping in the audio output, which seemed strange since it was using the proper sample rate all the way through ALSA. The issue was fixed by increasing the “buffer time” parameter from 20ms (the default) to 100ms. This is done by passing -a 100 when invoking squeezelite.
In addition to the squeezelite Squeezebox client, I installed ShairPort to allow audio streaming directly from any AirPlay capable device such as an iPhone, iPod Touch, or Mac OS X computer. The installation is a bit cumbersome and requires a lot of extra software to be installed including a compiler, etc. But it’s super convenient and easy to use once setup. At first it had very poor sound quality, but I was able to fix this by setting period_time=100000 in /etc/libao.conf. It seems to work best when buffer_size and period_size are equal in /proc/asound/card1/pcm0p/sub2/hw_params (ShairPort must be playing audio to verify those numbers).
Resetting ALSA configuration
It was necessary to reset the ALSA configuration numerous times throughout the process of setting up and debugging the PogoPlayer. This is rather more tricky than it should be. Here is the command sequence to do that:
chmod -x /etc/init.d/alsa-utils
chmod +x /etc/init.d/alsa-utils
Changes to the ALSA configuration sometimes do not work as expected if you forget to do this procedure. The most common reason I had to do this was when I wanted to remove a device such as a softvol control.