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:
- You may play stuff to the ALSA loop device even if JACK is not running or crashes.
- 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.