ALSA configuration

I recently configured the sound setup on my home computer. The problem was as follows: I have two sound cards 1) intel HDA (inbuilt) 2) usb based iMic. (I needed two because I wanted to have both my headphones and audio system hooked up and ready to play audio without manually (un)plugging cables.) I wanted to be able to switch between the cards on a per application basis. Basically enough capability to be able to launch applications each with a playback device determined at launch time.

Also, both the soundcards are pretty cheap and hence (probably) lack good re-sampling engines on the card itself. Most of my music is in 44.1KHz and iMic probably re-samples them to 48KHz during playback. intelHDA supports sample rates as high as 192KHz and I think does a naive re-sampling to that rate too before playback. So besides simple device switching I also desired to use a decent resampler to convert to 48KHz or 192KHz before the signal is fed to the device.

And last (but definitely not the least) I needed some way to mix multiple streams without latency issues.

Pulseaudio fits the above requirements very well. It also supports per application volume controls and dynamically switching applications to different input/output devices. It packages good resampling algorithms and sports a lot of features through modules. However, it doesn’t support 32 bit integer audio (which intel HDA does). Once pulseaudio supports 32bit integer devices natively I plan to switch. Given the recent interest in pulseaudio by Ubuntu and Fedora projects, this should happen fast.

So I took the alsa route. The environment variables ALSA_CARD (or ALSA_PCM_CARD) can be set to the card number to switch the card being used. This gives us an easy mechanism to launch different applications with different audio devices.

Also, alsa by default configures a ‘dmix’ plugin to mix audio streams before sending it to the hardware. However, the default configuration (buried in /usr/share/alsa) puts dmix before the hardware device selection. dmix has to operate on data at a fixed sampling rate which by default is set to 48KHz. Moreover, to avoid lots of CPU usage, the re-sampling algorithm used is poor. I wanted to setup separate dmix plugins for each of my devices operating at their native frequencies and use good re-samplers for audio sample rate conversions. Fortunately, alsa has a set of plugins (package alsa-plugins in gentoo) which include good re-samplers. Note that with two dmix plugins setup, using ALSA_CARD to switch the hardware device will not (and should not) work. We need a mechanism to switch to the given dmix plugin instance using environment variables.

Without further ado, here is my ~/.asoundrc and audio.sh (which I use for launching audio applications / switching devices / manipulating volume).

~/.asoundrc

defaults.pcm.rate_converter "speexrate"
defaults.pcm.card_name "intelHD"
defaults.pcm.card 0

pcm.!default {
        type plug
        slave.pcm {
                @func getenv
                vars [
                        ALSA_CARD_NAME
                ]
                default {
                        @func refer
                        name defaults.pcm.card_name
                }
        }
}

ctl.!default {
        type hw
        card {
                @func getenv
                vars [
                        ALSA_CARD
                ]
                default {
                        @func refer
                        name defaults.pcm.card
                }
        }
}

pcm.dsp {
        type plug
        slave.pcm "intelHD"
}

ctl.dsp {
        type hw
        card 0
}

pcm.intelHD {
        type dmix
        ipc_key 1024
        slave {
                pcm "hw:0,0"
                format S32_LE
                rate 192000
        }
        bindings {
                0 0
                1 1
        }
}

ctl.intelHD {
        type hw
        card 0
}

pcm.iMic {
        type dmix
        ipc_key 1025
        slave {
                pcm "hw:1,0"
                format S16_LE
                rate 48000
        }
        bindings {
                0 0
                1 1
        }
}

ctl.iMic {
        type hw
        card 1
}

and audio.sh

#!/bin/bash

h=`cat $HOME/.dwm/status_audio`;
if [ "x$h" == "x[ Spkr ] " ]; then
        export ALSA_CARD_NAME=intelHD ;
        export ALSA_CARD=0 ;
        master=Front ;
else
        export ALSA_CARD_NAME=iMic ;
        export ALSA_CARD=1 ;
        master=PCM ;
fi

if [ "x$1" == "x" ]; then
        if [ "x$h" == "x[ Spkr ] " ]; then
                echo -n "[ HeadPh ] " > $HOME/.dwm/status_audio ;
                export ALSA_CARD_NAME=iMic ;
                export ALSA_CARD=1 ;
                master=PCM ;
        else
                echo -n "[ Spkr ] " > $HOME/.dwm/status_audio ;
                export ALSA_CARD_NAME=intelHD ;
                export ALSA_CARD=0 ;
                master=Front ;
        fi
elif [ $1 == "up" ]; then
        amixer set $master 5%+ ;
elif [ $1 == "down" ]; then
        amixer set $master 5%- ;
elif [ $1 == "toggle" ]; then
        amixer set $master toggle ;
else
        exec $1 ;
fi

v=`amixer get $master | grep "Front Left:" | egrep -o "[0-9]+%"`
m=`amixer get $master | grep "Front Left:" | egrep -o "off"`

if [ "x$m" == "x" ]; then
        echo -n "vol:$v "
else
        echo -n "vol:(M) "
fi > $HOME/.dwm/status_volume

Let’s go over .asoundrc first. BTW, just for the record, the alsa configuration format is one of the weirdest formats I have seen. It is not user friendly and it also lacks a good deal of flexibility. Anyways, alsa configuration is all about defining pcm (virtual devices for audio I/O) and ctl (mixer interfaces) virtual devices. A number of pcm virtual device plugins along with some details on setting them up is documented at alsa-project. I use the ‘hw’, ‘dmix’ and ‘plug’ plugins in the above configuration. The pcm.intelHD, ctl.intelHD, pcm.iMic and ctl.imic sections outline dmix configurations for each device. The ‘type dmix’ creates dmix plugin instances named ‘intelHD’ and ‘iMic’. Each requires unique (arbitrarily chosen) ipc_keys. The slave configuration is pretty much the hardware device at its highest possible capabilities in format and sample-rate (pcm “hw:n,m” uses the hw plugin to talk directly to the kernel device; BTW, we could have used any pcm virtual device here). I don’t know what bindings are, but I am guessing it binds channels between the slave pcm device and the pcm being configured. I use two channels and the above seems to work.

Note that the above creates dmix virtual pcm device instances with the same format and sample rates as the slave pcm. So intelHD dmix can take signed integer 32bit data at 192KHz and the iMic dmix can take signed integer 16bit data at 48KHz. My devices don’t support float 32bit data (which pulseaudio supports :/). If we directly use these pcm devices for audio playback our audio application will either need to do format conversion/resampling itself or fail with format/samplerate incompability kind of errors. Fortunately, alsa provides a ‘plug’ virtual pcm device (very weird name if you ask me) to do the format conversion and resampling automatically. It can also do all kinds of channel routing magic too but I didn’t use any such capabilities. I basically created the ‘default’ pcm device (used by all alsa applications in case no pcm device is explicitly stated) using plug. This frees applications to match my dmix (and hardware device ) format/samplerates. Moreover, the default pcm ‘plug’ virtual device uses the ‘speexrate’ re-sampler for all resampling. The documentation at the url above says that plug supports a rate_converter argument for specifying the re-sampling engine to use. However, that didn’t work on my system. Fortunately changing the default resamler works. The speexrate resampler takes a little less than 20% CPU on a single core on my 2.4GHz Core2Duo. The CPU usage is reported as part of the audio application using the audio device. It would have been nicer on the CPU to be able to be able to create separate dmix instances for each sample rate in use by applications and then resample each of them to the hardware sample rate. But I guess that’s necessary only when one has a lot of simultaneous audio applications running and a lot of them at the same sample rate. Moreover, it will be a nightmare to configure that using the alsa configuration format :/.

The default pcm device looks up an environment variable ALSA_CARD_NAME to form its slave pcm device. I can give any pcm name here but the intent is to configure it to either “intelHD” or “iMic” to use the dmix pcms. Note the really weird syntax. I took me a few trials and inspiration from /usr/share/alsa/alsa.conf to get this right. I had to use two different environment variables for the defaut mixer (ctl) and default pcm. This is because I couldn’t figure how to use a slave with ctl.!default. (The !mark is for overriding a previous configured pcm).

This completes alsa configuration. Now let’s come to audio.sh. It looks up the status_audio file to check which audio device should be selected for the to be performed operation. If status_audio contains “[ Spkr ]” then intelHD is used, otherwise iMic is used. Its usage is as follows:

audio.sh <command>
runs the command with the selected audio device as the default pcm device. Basically, it sets up the the environment variables ALSA_CARD_NAME and ALSA_CARD and starts the command.

audio.sh up
Turns up volume by 2%. Note the usage of $master. The volume control that’s effective on the two devices are different. $master lets amixer use the appropriate control for each device.

audio.sh down
Turns down volume by 5%. (I use different values for up and down so that I can turn down very loud music really fast).

audio.sh
Selects the other device than the current. It writes to the status_audio file to preserve the setting.

audio.sh also writes the volume of the device being configured to status_volume. I use status_audio and status_volume for displaying audio status in dwm (which is a fantastic window manager, if you like a minimalistic and highly productive windowing environment).

Advertisements
This entry was posted in Computing, Linux. Bookmark the permalink.

One Response to ALSA configuration

  1. Pingback: Pulseaudio + ALSA Configuration « Defective Compass

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s