For ages, I have been tinkering with ALSA and for an equally long time, I have been frustrated by the rather opaque configuration system it uses. It is really powerful, no doubt, but getting what I want is difficult.

Basically, I want my plain stereo setup to be expanded a bit with a subwoofer to add a bit more club-like sound at times. The audio sources I use do not have a sub channel, however, so how do I...

  • Upmix my regular 2.0 stereo sources to 2.1 (where the .1 is the sub channel)
  • Filter the sub channel so the sub only handles low frequencies (i.e. make a crossover filter)

ALSA has the upmix and ladspa plugins. One handles mixing channels while the other is able to apply a variety of digital signal processing filters. I have tried and tried but ALSA kept throwing really hard to interpret errors at me. I have had upmixing and filtering work separately, but chaining them together never went well.


The configuration below handles my situation just right, and is based on this post which describes something very similar. I will comment on the different sections to convey the bits that I do understand. Some of it, I don't really get but I accept that they are necessary to make things work. Please add explanations if you are able to refine further upon this:

ALSA configurations are basically a chain of blocks that refer to each other. One block hands off a set of audio channels to be processed by another by referring to the next block in line with the slave tag. The first tag in my config specifies the default device, i.e. the audio device apps use if nothing else is specified

pcm.!default "plug:upmix21"

The upmix plugin handles mixing the input channels to a different number of channels. Here, we go from 2.0 to 5.1, since the upmix plugin only handles n channels where n%2=0 and because it lets me avoid using a route plugin that I never managed to get working

pcm.upmix21 {
  type upmix
  slave.pcm "lowpass_51to51_plug"
  delay 0
  channels 6

The pcm.lowpass_51to51_plug is a syntactic necessity because the upmix21 block only seems to accept plug type blocks as slaves.

pcm.lowpass_51to51_plug { 
  type plug 
  slave.pcm "lowpass_51to51" 

This bit handles invoking the LADSPA filters. There are quite a few of them. On Ubuntu, Raspian and so on, they come with the cmt and swh-plugins packages. They can be used for all sorts of things - ALSA is just one. On your installation, you should check that path points to whereever the ladspa filters live on your system.

In this configuration, I use two plugins, referred to by their IDs. 1098 is a passthrough filter that just copies input to output, but without it, channels not specifically mentioned will get no sound. The syntax is a little odd, because even though you only specify input and output channel 0, all channels are copied. That is just how it works, apparently. Filter 1051 is a low pass filter that accepts a crossover frequency - 120Hz in my case. I apply it to channel 5, i.e. the LFE or subwoofer channel in the upmixed stereo signal.

pcm.lowpass_51to51 { 
   type ladspa 
   slave.pcm "upmix_51to51_float" 
   path "/usr/lib/ladspa" 
   channels 6
   plugins { 
      0 { 
         id 1098  # Identity (Audio) (1098/identity_audio) 
         policy duplicate 
         input.bindings.0 "Input"; 
         output.bindings.0 "Output"; 

      1 { 
         id 1051  # Low-pass filter. 
         policy none 
         input.bindings.5 "Input"; 
         output.bindings.5 "Output"; 
         input { 
            controls [ 120 ] # No higher than 120 Hz - nothing good.

This next bit I don't quite get but it is necessary - otherwise, ALSA will barf, saying something about format incompatibility. Basically, we set the audio format to S16_LE (stereo 16 bit low-endian?). I would think that this had been established already, but I accept that I need this - please add an explanation here if you can.

pcm.upmix_51to51_float { 
  type lfloat 
  slave.pcm "21route" 
  slave.format "S16_LE" 

As I was testing, I actually used a 5.1 setup to figure out what did and did not work. This block lets me mute channels I don't want. The ttable holds the mappings in the form <input channel>.<output channel> <volume>. This is actually the final block as it's slave is surround51 - a pcm device that comes with my ASUS Xonar U7 sound card. Check aplay -l and aplay -L to see what your devices are called.

pcm.21route {
  type route
  slave.pcm "surround51"
  ttable {
        0.0     1       # left channel
        1.1     1       # right channel
        2.2 0           # mute
        3.3 0           # mute
        4.4 0           # mute
        5.5 1           # sub channel

That is it. I don't know how long time I have spent on this in total. It has been an on and off project for a long time, but now it is actually working. I hope this helps someone spend less time on this than I have.

As a side note, I have mpd and upmpdcli running on top of this on a RasPi 2. Using BubbleUPNP on Android and SWYH on Windows, this lets me stream Google Music, my own media and almost anything else to my super swanky setup. Yay!

EDIT: Things I still need to do:

  • I actually don't have a volume control that works with BubbleUPNP yet. I need a proper ALSA ctl definition. Not a big problem really - I just use alsamixer to crank up the volume to 100% per default - then I manually (yes, shocking) adjust the volume on my amplifier.
  • I want to be able to redirect all audio from my Android devices, but I can only do this using rooted devices and the Xposed framework. That works very well, but I want to be able to do this wo. the root stuff.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.