While the sound architecture in Windows desktops is far from ideal, it’s still one of the most comfortable setups, at least from the user experience perspective. And when it works and you don’t have to fix it.

Meanwhile, ALSA in Linux has really made good progress. Most of the time, it does not require any configuration to deliver sound to desktop applications right after installing your favourite distribution.

Still, it gets a bit tricky any time you want to do something more interesting with your sound devices. On the side of challenges, there’s no other way than writing a proper asound.conf. On the other hand, anything is possible. In this article I will explain a configuration of up to 3 devices:

  • 1st sound device – an intel chip on the motherboard. This one could be connected to a set of speakers, for example. I’m not using it right now.
  • 2nd sound device – an audio chip onboard of my nVidia card, with a HDMI cable connected to my monitor, which has a built in speaker. Good whenever I want my ears to rest and get the sound from this speaker.
  • 3rd device – Fiio USB-bluetooth adapter. It presents itself as an USB audio device, and has a transceiver for bluetooth to communicate with my headset

In a Windows-like setup, you’d like to simply connect all 3 devices and switch between them within a few mouse clicks. Well, in this setup, the Linux version requires one click less 😉 Also, I’ve decided to cut out all the overhead, so there’s no pulseaudio, JACK or any other extras, just plain ALSA. Let’s begin.

First, kernel drivers are needed. This step can be skipped unless you manage your own kernel config. In such case, the typical drivers are needed:

  • sound card support
  • snd-hda-intel
  • CONFIG_SND_USB

The alsa-utils package provides a simple client to play audio files called aplay. It also allows listing sound devices, which is needed to build the configuration.

fst@ryba ~ % aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: Generic Analog [Generic Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 1: Generic Digital [Generic Digital]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: NVidia [HDA NVidia], device 3: HDMI 0 [ROG PG27AQ]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: NVidia [HDA NVidia], device 7: HDMI 1 [HDMI 1]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: NVidia [HDA NVidia], device 8: HDMI 2 [HDMI 2]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: NVidia [HDA NVidia], device 9: HDMI 3 [HDMI 3]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: PRO [FiiO BTA30 PRO], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

What’s worth noting from the above:

  • card 0 remains unused. You can use it by declaring slave devices under hw0,0 and hw0,1 for analog and digital respectively.
  • card 1 is the NVidia chip connected to my monitor
  • card 2 is the USB device

And here’s the /etc/asound.conf (you can also declare your config locally. As per the documentation, changing the config does not require restarting the ALSA service. Only restart the sound client such as mplayer).

fst@ryba ~ % cat /etc/asound.conf
# declaration of the hdmi hardware device.
pcm.hdmi_hw {
  type hw
  card 1
  device 3
}

# as above, but for the usb device. Both are needed
# so ALSA could send sound one signal to both outputs
pcm.fiio_hw {
  type hw
  card 2
  device 0
}

# also create a device for the card 0 if you require

# the HDMI monitor has a hardware volume control.
# that's not too convenient, so this ALSA plugin will add a
# software volume contorl to the device connected via HDMI
pcm.hdmi_sv {
  type softvol
  slave.pcm hdmi_hw 
  control.name hdmi_volume
  control.card 1
}

# this part uses the 'multi' plugin to get sound to both devices
# most of the time, the hdmi device can stay silenced or just muted (1 click)
pcm.both {
    type multi
    slaves.a.pcm "fiio_hw"
    slaves.a.channels 2
    slaves.b.pcm "hdmi_sv"
    slaves.b.channels 2
    bindings.0 { slave a; channel 0; }
    bindings.1 { slave a; channel 1; }
    bindings.2 { slave b; channel 0; }
    bindings.3 { slave b; channel 1; }
}

# the 'route' plugin is needed to reroute the channels for stereo
# as you can see, it refers the previous multi pcm device
pcm.stereo2both {
    type route
    slave.pcm "both"
    ttable.0.0 1
    ttable.1.1 1
    ttable.0.2 1
    ttable.1.3 1
}

# finally the route->multi->softvol->hw device path is registered
# as the default PCM device, for any application to use.
pcm.!default stereo2both

# some apps don't use the PCM interface, chosing CTL instead
# such as alsamixer. This allows opening the usb device by default
# the '!' char replaces default configuration with creating the
# one specified in this config
ctl.!default {
    type hw           
    card 2
}

Switching between the devices can be achieved simply using a mixer app, or alsamixer in the terminal. For XFCE, I use xfce-mixer, but there are mixers also for Gnome or KDE.