Configuring ALSA for pro hardware setups

Advanced .asoundrc / asound.conf examples

Nowadays most Linux distributions probably use PulseAudio for audio. In my opinion, only a couple of years ago it was completely unusable. I still find it mostly unnecessary and as such do not use it on my main rig. The biggest issues I've encountered with PulseAudio are latency problems. On a few laptops I've also had bad experiences with autoconfig guessing wrong inputs and outputs and screwing up the mixer each time you do something like connect your headphones. I try to use PulseAudio only for applications which explicitly require it, and there's pretty much only one such application I use regularly.

Here I'll introduce some specific scenarios regarding asound.conf. I find using /etc/asound.conf perhaps a bit more logical than ~/.asoundrc, as the system hardware usually does not change with the user. I will refer to either one of these as the config file in the article. Use whatever suits you best. My full ALSA configuration file is in my GitHub dotfiles repo, or you can download it here as it is as I'm writing this.

Note: I have multiple audio devices on my rig. One is my motherboard's integrated, which I use when watching cat videos etc. I also have the HDMI output of my display adapter, and an USB DAC. My main device is a dedicated ESI Juli@ PCI audio interface which I use without a mixer device. This allows me to play stuff through it at whatever sample rate without any resampling necessary, so I use it for listening to music and watching movies. It's also actually pretty handy when you have a dedicated card like this, you are sure that no website suddenly starts making audible noise. If I do wish to listen to a website, I just enable monitoring from the ESI Juli@ with a click of a button - my motherboard's integrated audio is connected to Juli@'s inputs physically.

PulseAudio as a pipe to ALSA

This isn't directly related to ALSA configuration, but if you do configure your ALSA, you probably would like to configure PulseAudio also.

If you have an application that absolutely requires PulseAudio, but you want to use your otherwise working audio setup, you may configure PulseAudio so that it only spawns itself when necessary, pipes the audio to an ALSA device, and closes itself when not needed anymore.

There's a really good example of that in ArchWiki.

Changing default device by an environment variable

In your config file, define the default device as follows:

# Default device.
pcm.!default {
    @func refer
    name { @func concat 
        strings [ "pcm."
            { @func getenv
                vars [ ALSA_DEFAULT_PCM ]
                default "combo"
            }
        ]
    }
}

# Default control device.
ctl.!default {
    @func refer
    name { @func concat 
        strings [ "ctl."
            { @func getenv
                vars [ ALSA_DEFAULT_CTL ]
                default "hda_hw"
            }
        ]
    }
}

Now, you can setup the default device with environmental variables ALSA_DEFAULT_PCM and ALSA_DEFAULT_CTL. The control device is required by some applications, but usually the default device is sufficient to determine. This also enables you to simply change the default device for single applications. In the example I've set combo device and hda_hw control device as fallback devices if the environment variables haven't been set. I normally have in my .xinitrc file the following:

export ALSA_DEFAULT_PCM="julia"
export ALSA_DEFAULT_CTL="julia_analog_hw"

Different playback and recording devices as one

If you define an asymmetric ALSA device, you may connect it's capture and playback parts to different physical devices like this:

# Asymmetric device for audio with input.
pcm.combo {
    type asym
    playback.pcm "hda_out_mix_44100_vol"
    capture.pcm "julia_ain_mix_44100"
    hint {
        show on
        description "Combo asymmetric"
    }
}

Simple recording

For capturing something with minimum overhead, a setup like this will output sound at the hda device while also recording to a WAV file:

# WAV recording device.
pcm.file_out {
    type file
    slave.pcm "hda"
    file /dev/shm/alsaout.wav
    format "wav"
    hint {
        show on
        description "WAV recording"
    }
}

ALSA equalizer

If you'd like to use ALSA's equalizer capabilities, there's an equal device type you may use. The following setup will create a device hda_eq which will have a corresponding graphical equalizer device in alsamixer, and the sound will eventually come out from the hda device:

# Virtual equalizer device.
pcm.hda_eq {
    type plug
    slave.pcm {
        type equal
        slave.pcm "hda"
    }
    hint {
        show on
        description "HDA with equalizer"
    }
}

# Control device for equalizer plugin.
ctl.eq {
    type equal
}

Defining a basic mixing device

First, we need the hardware devices:

# Juli@ analog in/out.
pcm.julia_analog_hw {
    type hw
    card Juli
    device 0
    subdevice 0
}

# Control device for program compatibility.
ctl.julia_analog_hw {
    type hw
    card Juli
}

Then we may define a mixing ALSA device. A non-mixing device will only allow one audio stream to play at a time, whereas a mixing device will mix multiple audio streams into one.

# Mixing with Juli@, analog out, dmix.
pcm.julia_aout_mix_44100 {
    type plug
    slave.pcm {
        type dmix
        ipc_key 11
        slave {
            pcm "julia_analog_hw"
            format S32_LE
            periods 16
            period_time -1
            period_size 1024
            rate 44100
        }
    }
    hint {
        show on
        description "Juli@ dmix analog out 44.1 kHz"
    }
}

The ipc_key is just an arbitrary unique identifier within the config file. The slave device specifications may be found out by trial and error if you don't have any specifications.

Multiple interfaces as one card

First, let's define outputs for the ESI Juli@:

# Analog out.
pcm.julia_aout {
    type plug
    slave {
        format S32_LE
        pcm "julia_analog_hw"
    }
    hint {
        show on
        description "Juli@ analog out"
    }
}

# Digital out.
pcm.julia_dout {
    type plug
    slave {
        format S32_LE
        pcm "julia_digital_hw"
    }
    hint {
        show on
        description "Juli@ digital out"
    }
}

Now we can define a virtual device julia_qout which will have four separate output channels (two from the analog out, two from the digital out), or, we can define a device julia_bout which will clone a stereo output to both analog and digital output:

# Both analog and digital out, channels separate (quad).
pcm.julia_qout {
    type plug
    slave {
        format S32_LE
        pcm {
            type multi
            slaves.dout.pcm "julia_dout"
            slaves.dout.channels 2
            slaves.aout.pcm "julia_aout"
            slaves.aout.channels 2
            bindings.0.slave dout
            bindings.0.channel 0
            bindings.1.slave dout
            bindings.1.channel 1
            bindings.2.slave aout
            bindings.2.channel 0
            bindings.3.slave aout
            bindings.3.channel 1
        }
    }
    route_policy duplicate
    ttable {
        0.0 1
        1.1 1
        2.2 1
        3.3 1
    }
    hint {
        show on
        description "Juli@ analog/digital out"
    }
}

# Both analog and digital out, channels duplicated.
pcm.julia_bout {
    type plug
    slave {
        format S32_LE
        pcm {
            type multi
            slaves.dout.pcm "julia_dout"
            slaves.dout.channels 2
            slaves.aout.pcm "julia_aout"
            slaves.aout.channels 2
            bindings.0.slave dout
            bindings.0.channel 0
            bindings.1.slave dout
            bindings.1.channel 1
            bindings.2.slave aout
            bindings.2.channel 0
            bindings.3.slave aout
            bindings.3.channel 1
        }
    }
    route_policy duplicate
    ttable {
        0.0 1
        1.1 1
        0.2 1
        1.3 1
    }
    hint {
        show on
        description "Juli@ analog/digital out"
    }
}

Software volume control

Sometimes a device doesn't have volume control, but it would be nice if it had. The following example will create a volume control device julia_vol, which will output it's sound to julia_dout eventually:

# Digital out volume control.
pcm.julia_vol {
    type softvol
    slave {
        pcm "julia_dout"
    }
    control {
        name "DigitalMaster"
        card Juli
    }
    hint {
        show on
        description "Juli@ digital out with volume control"
    }
}

You may need to play something through the volume control device first before it is visible to some applications. For example, at some point I used a system tray application called volumeicon. For it to see the device, I had the following in my .xinitrc:

# Play a dummy wav so that software volume control device is visible for volumeicon.
aplay -q -D julia_vol ~/.dummy.wav
exec volumeicon &

where dummy.wav was an empty WAV file.

Controlling the volume

I use a script like this to set the volume via multimedia keys:

#!/bin/bash

current_vol=$(amixer -cPCH get 'Soft Master',0 | grep "Front Left:" | sed "s/[^0-9]*\([0-9]*\).*/\1/")

if test "$1" == "up" || test "$1" == "+"; then
    new_vol=$(expr $current_vol + 5)
    if [ $new_vol -gt 255 ]; then
        new_vol=255
    fi
elif test "$1" == "down" || test "$1" == "-"; then
    new_vol=$(expr $current_vol - 5)
    if [ $new_vol -lt 5 ]; then
        new_vol=5
    fi
fi

amixer -cPCH set 'Soft Master',0 $new_vol

You just give the script either an "up" (or "+") or a "down" (or "-") as an argument. The PCH as the card refers to the hardware card name, obtainable by running the command aplay -L.

Downmixing 5.1 to stereo (for movies)

Here's an example how you may mix six audio channels down to two:

# General movie setup.
pcm.julia_movie {
    type plug
    slave {
        format float
        pcm {
            type multi
            slaves.speakers {
                pcm "julia_aout"
                channels 2
            }
            bindings.0.slave speakers
            bindings.0.channel 0
            bindings.1.slave speakers
            bindings.1.channel 1
        }
    }
    route_policy duplicate
    ttable {
        0.0 0.185
        1.1 0.185
        2.0 0.185
        3.1 0.185
        4.0 0.13
        4.1 0.13
        5.0 0.5
        5.1 0.5
    }
    hint {
        show on
        description "Juli@ movie setup"
    }
}

Here I have boosted the strength of LFE channel, and duplicated it to both left and right channel. Note that the sum of everything per channel must be no bigger than 1.0, otherwise you may encounter serious distortion.

A balanced matrix would have a routing table like this:

ttable {
    0.0 0.29289
    1.1 0.29289
    2.0 0.29289
    3.1 0.29289
    4.0 0.20711
    4.1 0.20711
    5.0 0.20711
    5.1 0.20711
}

If you'd like to have a bit stronger dialog volume for example, just raise the multiplier for the channel number four (but remember to lower others accordingly).

Dummy device

In some situations, for example when testing or debugging applications, it might be nice to have no real audio output. You may define a null device as follows:

# Null output.
pcm.null {
    type null
}

Anything you output to the null device will be gone. This is like redirecting audio to /dev/null.

LADSPA plugins

You may also use LADSPA plugins in ALSA config if you have the plugins installed.

Binaural device

If you'd like a binaural virtual surround effect from your headphones, a setup like this will output everything put to julia_binaural through the Bauer binaural plugin:

# Plug device for bs2b.
pcm.julia_binaural {
    type plug
    slave {
        rate 44100
        pcm {
            type ladspa
            slave.pcm "julia_digital"
            path "/usr/lib/ladspa"
            plugins [ {
                label bs2b # Bauer stereophonic-to-binaural DSP
                input {
                    controls [ 2 0 ] # crossfeed level (1-3), high boost (0, 1)
                }
            } ]
        }
    }
    hint {
        show on
        description "Juli@ binaural"
    }
}

The label in plugin definition is the one you get by running the LADSPA tool analyseplugin to against a plugin file.

Cut low bass

If your low bass is going through walls and disturbing people in the night, a setup like this might do the trick:

# LADSPA device for bass cut.
pcm.ladspa_basscut {
    type ladspa
    channels 2
    slave.pcm "julia_dout"
    path "/usr/lib/ladspa"
    plugins [
        {
            label mbeq # Multiband EQ (mbeq_1197.so)
            policy duplicate
            input.bindings.0 "Input"
            output.bindings.0 "Output"
            input.controls [-70 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
        }
    ]
}

Loop devices

Loop devices are an extremely handy tool for working with JACK or virtual machines. For use with KVM virtual machines as dedicated audio devices, see my previous article KVM audio with ALSA loopback.

For JACK, we may define a setup like this:

# Loop device substreams in device 0 correspond to substreams in device 1. In reality there are no ins and outs, just ends (they work both ways).

pcm.loop_out_alsa {
    type plug
    slave.pcm "hw:Loopback,0,0"
    hint {
        show on
        description "ALSA loopback out"
    }
}

pcm.loop_out_jack {
    type plug
    slave.pcm "hw:Loopback,1,0"
}

pcm.loop_in_alsa {
    type plug
    slave.pcm "hw:Loopback,0,1"
    hint {
        show on
        description "ALSA loopback in"
    }
}

pcm.loop_in_jack {
    type plug
    slave.pcm "hw:Loopback,1,1"
}

Now, run a script like this:

#!/bin/bash
echo "ALSA/JACK bridge is active."
alsa_in -j "ALSA output" -dloop_out_jack &
alsa_out -j "ALSA input" -dloop_in_jack &

Now whatever you play to the alsa device loop_out_alsa will be available to JACK as ALSA output, and similarly for the inputs. I think it's easier to think the loop devices as directed pipes, even though they work both ways. It just makes naming and handling devices a bit more easier.

There are two benefits you gain from a setup like this:

  1. You may play stuff to the ALSA loop device even if JACK is not running or crashes.
  2. You may do stuff in JACK even if the ALSA application is not running or crashes.

Normally, if either the ALSA end or JACK end is shut down (due to application crash for example), the connection is lost and you have to set up everything again after restarting things. Using the ALSA loop device between the two eliminates this problem. Thus, you may save a killer JACK setup with complicated routing scheme, plugins and whatever, and load it even before the ALSA applications are running.

Send me email or comment below:
(Please note: comments with direct links to commercial sites might not be published.)
Creative Commons License  This article by Olli Helin is licensed under the Creative Commons Attribution 4.0 International License
Powered by GainCMS