Independent ALSA and linux audio support site

Jack and Loopback device as Alsa-to-Jack bridge

By Thorgal - May 3rd 2010

Contents

Introduction

Some people may use PCs where the Jack Audio Connection Kit is running all the time. As one of these users myself, my DAW PC uses a very light WM (fluxbox) without any of the audio layers provided by the more feature rich WMs (like KDE or Gnome). I have jack started at login and hopefully, it never goes down until the next PC shutdown. Since I do not want any other audio layer such as Pulseaudio, etc, in between Jack and ALSA in my case (but it could well be FFADO for firewire devices), this has the slight disadvantage of making non jackified applications unusable.

So how can one provide a permanent bridge between non jackified applications and Jack\? Well, there are different ways. One can be purely hardware: enable another soundcard (e.g the onboard sound chip) and physically link it to your DAW sound-card if you are like me with a dedicated audio h/w for DAW operations. Let this extra soundcard be the default (ALSA index 0) so that apps like flashplayer, skype, etc, use it by default. However, this may limit the number of h/w IOs of your DAW soundcard for your pro work. I do not like this physical link because my RME Multiface II has only 8 analog mono INs, while the onboard sound chip (Intel HDA) has no digital output that I can link to the Multiface digital input. I would be forced to patch two INs of the Multiface to the stereo output of the onboard chip. It is too expensive to consider in terms of physical IOs.

The alternative is a software solution or a mix h/w - s/w (as used in my final setup). So, I was looking for a solution in the form of permanent Jack clients, playback and capture ideally, or at least playback since I can use the capture device of a second soundcard like the onboard chip or anything else (I will clarify this further down but it is not necessarily an average setup). In terms of software solutions, the ALSA jack PCM plugin is in my opinion not ideal because the Jack client will disappear as soon as the application stops outputting audio. Furthermore, this PCM plugin has not been updated (except lately by Torben Hohn but the patch is not widespread) and I found the plugin quite buggy / unstable in many situations.

It is not until recently, as I was fiddling with the ALSA Loopback device, that I saw a way to achieve what I needed.

The ALSA Loopback 'Sound card'

The ALSA Loopback sound card is a virtual soundcard that is created once the ALSA kernel module snd-aloop is loaded. This virtual soundcard device, as its name indicates, sends back the output signal of applications using it back to itself, so one has a chance to e.g. record this signal from the same device. Simply imagine that you have a physical link between one OUT and one IN of the same device.

By default, the Loopback sound card consists of 2 devices, each composed of 8 subdevices. Once the kernel module snd-aloop is loaded, you can verify that the sound card has been created:

~$ aplay -l

**** List of PLAYBACK Hardware Devices ****

card 0: DSP [Hammerfall DSP], device 0: RME Hammerfall DSP + Multiface [RME Hammerfall DSP + Multiface]
 Subdevices: 0/1
 Subdevice #0: subdevice #0
card 1: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]
 Subdevices: 7/8
 Subdevice #0: subdevice #0
 Subdevice #1: subdevice #1
 Subdevice #2: subdevice #2
 Subdevice #3: subdevice #3
 Subdevice #4: subdevice #4
 Subdevice #5: subdevice #5
 Subdevice #6: subdevice #6
 Subdevice #7: subdevice #7
card 1: Loopback [Loopback], device 1: Loopback PCM [Loopback PCM]
 Subdevices: 8/8
 Subdevice #0: subdevice #0
 Subdevice #1: subdevice #1
 Subdevice #2: subdevice #2
 Subdevice #3: subdevice #3
 Subdevice #4: subdevice #4
 Subdevice #5: subdevice #5
 Subdevice #6: subdevice #6
 Subdevice #7: subdevice #7

Note that you can control the number of subdevices with the module option pcm_substreams (8 by default). You can always set it to 2 only if you wish at loading time. As an example, here is my ALSA module config file (/etc/modprobe.d/sound.conf on my debian-based DAW)

alias snd-card-0 snd-aloop
alias snd-card-1 snd-hdsp
alias snd-card-2 snd-hda-intel

options snd-aloop index=0 pcm_substreams=2
options snd-hdsp index=1
options snd-hda-intel index=2

As you can see, I do fix indexes even though ALSA and Jack can work with names only. It is motivated by the special position that is index 0, the ALSA default device that flashplayer will try to use.

Compiling snd-aloop if needed

Update: it may not be needed any longer as of kernel 2.6.38 ...

It may well be that the ALSA Loopback kernel module was not included in your distribution's kernel package (it is the case in e.g. debian, as far as I know). This is no bother as we can easily compile it. Note that there is no way around since the loopback ALSA module is not part of the kernel baseline in general. So unless your kernel packager had done the following work, you will have to do it yourself ...

Warning: I tried alsa-driver 1.0.21 against 2.6.33.5-rt22 and while it compiled fine, it would not load at all, even when forced. So don't waste your time with this version combo.

Make sure you really don't have it installed. Better check that not :)

sudo modinfo snd-aloop

If modinfo reports nada, time to check that you have installed the kernel headers corresponding to your presently running kernel. I'll leave this to you as this is very distro dependent. In debian based distros, the package is called something like linux-headers-xxx and must match the installed kernel (package linux-image-xxx).

Time to make a backup of the installed kernel modules. Example;

cd
mkdir backup  
cd backup
cp -a /lib/modules/`uname -r`/kernel/sound  .

Prerequisite: you of course need a compiler and other tools. In debian based distros, you can check that you have a package called build-essential installed:

dpkg -l build-essential

If not, just get it:

sudo apt-get install build-essential

Now grab the alsa-driver source code (same version as your installed ALSA, in my case 1.0.23 which I will use in my description) from the The ALSA website, uncompress, untar it and cd to the alsa-driver top dir. Here is a command summary ...

cd
mkdir source
cd source
wget ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.23.tar.bz2
tar jxvf alsa-driver-1.0.23.tar.bz2
cd alsa-driver-1.0.23

Now you have to configure the source package for compilation. To help you, look at what ALSA modules are currently loaded:

cat /proc/asound/modules

And check what card they correspond to by typing

./configure --help

You will see a big list of possible cards. Pick the ones you are interested in. As an example, this is how I configured the alsa-driver source on my DAW system:

./configure --with-cards=hdsp,loopback,hrtimer --with-oss=yes --with-sequencer=yes

and on my laptop:

./configure --with-cards=intel8x0,loopback,hrtimer --with-oss=yes --with-sequencer=yes

So, feel free to configure it the way you want it. Once you have configured the ALSA driver source, you just go through the usual sequence:

make
sudo make install

It will normally install all the compiled modules into the correct location of your kernel installation. Now check that the kernel knows about the loopback module:

~$ sudo modinfo snd-aloop
filename:       /lib/modules/2.6.32/kernel/sound/drivers/snd-aloop.ko
license:        GPL
description:    A loopback soundcard
author:         Jaroslav Kysela <[email protected]>
srcversion:     B85A5847D027749DCF96195
depends:        snd-pcm,snd
vermagic:       2.6.32 SMP preempt mod_unload modversions CORE2
parm:           index:Index value for loopback soundcard. (array of int)
parm:           id:ID string for loopback soundcard. (array of charp)
parm:           enable:Enable this loopback soundcard. (array of bool)
parm:           pcm_substreams:PCM substreams # (1-8) for loopback driver. (array of int)

Allrighty, time to load it. But before that, shut down all audio apps (including firefox). Once done, do this:

sudo alsa force-unload  
sudo modprobe snd-whatever-module-you-need 
sudo modprobe snd-aloop 

Now, see if it works:

~$ lsmod | grep aloop
snd_aloop               4732  0
snd_pcm                57065  6 snd_aloop,snd_hdsp
snd                    40404  18 snd_aloop,snd_hdsp,snd_pcm,snd_hwdep,snd_rawmidi,snd_seq,snd_timer,snd_seq_device

If all was cool and dandy, just add snd-aloop in /etc/modules. (If you wish, you can give the loopback soundcard another name than "Loopback" in a modprobe option but I kept the default throughout the entire HOWTO and there is no need to change it.)

In case anything went wrong and you wish to go back to your previous ALSA installation, no problem:

sudo rm /lib/modules/`uname -r`/kernel/sound
sudo cp -a ~/backup/sound /lib/modules/`uname -r`/kernel/
sudo alsa force-reload

Understanding the ALSA Loopback sound card structure

Well, this is not too difficult to grasp. This virtual sound card consists of 2 devices:

If an application outputs its audio to one of the subdevices e.g. say hw:Loopback,0,0 the audio will be available as input in the corresponding subdevice hw:Loopback,1,0 because the whole point for this card is to send the signal back to itself.

So the generic principle is that an output signal to subdevice hw:Loopback,i,n becomes an input signal from hw:Loopback,j,n with

i = [0..1]
j = ~i (meaning if i = 0, j = 1 and vice-versa)
n = [0.. (s-1)] with s = number of subdevices (controlled by module option pcm_substreams)

Building an asoundrc file

The goal is to create a default ALSA plug device out of the Loopback card. For a complete software solution, we need one PCM playback, so ALSA apps can send audio to it, one PCM capture, so ALSA apps can get audio from it, and combine these 2 PCMs into a nice full duplex "plug" device.

Note that the underlying goal is this: I want the audio of my jack system capture ports (from my RME card) to be available at the ALSA capture device and vice-versa: hear from my jack system playback ports what ALSA apps are playing back to the ALSA playback device. Tricky...

asoundrc definition

The asoundrc below should work in most situations.

# playback PCM device: using loopback subdevice 0,0
pcm.amix {
  type dmix
  ipc_key 219345
  slave.pcm "hw:Loopback,0,0"
}

# capture PCM device: using loopback subdevice 0,1
pcm.asnoop {
  type dsnoop
  ipc_key 219346
  slave.pcm "hw:Loopback,0,1"
}

# duplex device combining our PCM devices defined above
pcm.aduplex {
  type asym
  playback.pcm "amix"
  capture.pcm "asnoop"
}

# ------------------------------------------------------
# for jack alsa_in and alsa_out: looped-back signal at other ends
pcm.ploop {
  type plug
  slave.pcm "hw:Loopback,1,1"
}

pcm.cloop {
  type dsnoop
  ipc_key 219348
  slave.pcm "hw:Loopback,1,0"
}

# ------------------------------------------------------
# default device

pcm.!default {
  type plug
  slave.pcm "aduplex"
}

In summary:

 * ALSA playback = subdevice 0,0
 * ALSA capture  = subdevice 0,1 
 * Jack readable client (cloop) = subdevice 1,0
 * Jack writable client (ploop) = subdevice 1,1

This asoundrc is very generic and one can of course tailor it in terms of sample rate, audio format, buffer size, etc. One can find the relevant parameters in the ALSA-lib documentation.

Some html5 browsers (i.e. Firefox 30; Chrome 38) will fail to open the pcm.!default device for audio playback. This can be fixed by using pcm.card0 instead:

# default device
pcm.card0 {
  type plug
  slave.pcm "aduplex"
}

Here is an example applicable to my DAW. I left some notes so you understand the extra stuff I also removed unnecessary dsnoop's and dmix's because when you analyse things a bit more, you realize that some of the ALSA PCMs will only be used by one single client (alsa_in/out) so there is no need to use dmix / dsnoop. Dmix only makes sense for the ALSA playback PCM because you can have more than one client outputting to ALSA at the same time. Anyway, note the hardware parameters I have added so that it matches my RME Multiface II requirements. For the dmix buffering parameters, read on below.

# hardware 0,0 : used for ALSA playback
pcm.loophw00 {
  type hw
  card Loopback
  device 0
  subdevice 0
  format S32_LE
  rate 96000
}

# playback PCM device: using loopback subdevice 0,0
# Don't use a buffer size that is too small. Some apps 
# won't like it and it will sound crappy 

pcm.amix {
  type dmix
  ipc_key 219345
  slave {
    pcm loophw00
    period_size 4096
    periods 2
  }
}

# software volume
pcm.asoftvol {
  type softvol
  slave.pcm "amix"

  control { name PCM }

  min_dB -51.0
  max_dB   0.0
}

# for jack alsa_in: looped-back signal at other ends
pcm.cloop {
  type hw
  card Loopback
  device 1
  subdevice 0
  format S32_LE
  rate 96000
}

# hardware 0,1 : used for ALSA capture
pcm.loophw01 {
  type hw
  card Loopback
  device 0
  subdevice 1
  format S32_LE
  rate 96000
}

# for jack alsa_out: looped-back signal at other end
pcm.ploop {
  type hw
  card Loopback
  device 1
  subdevice 1
  format S32_LE
  rate 96000
}

# duplex device combining our PCM devices defined above
pcm.aduplex {
  type asym
  playback.pcm "asoftvol"
  capture.pcm "loophw01"
}

# default device
pcm.!default {
  type plug
  slave.pcm aduplex

  hint {
       show on
       description "Duplex Loopback"
  }
}

Testing our new default ALSA device

Save this asoundrc config into $HOME/.asoundrc but make sure before that you are not overwriting an existing asoundrc file (back up whatever you have if it already exists).

OK, now we can test it from the command line. If jack is running on your other hardware (RME card in my case), you of course will not hear anything since we have not bridged yet our default ALSA device to the jack graph.

mplayer -ao alsa some_audio_file

You can use another app (aplay for example). The idea is that an ALSA app using the default device we have just created will not spit error messages and will play along nicely. Try for example lmms using the ALSA default :)

The Jack Bridge

OK, this is where it will get a little bit confusing because of the loopback nature of the virtual device ;)

Creating permanent Jack clients using alsa_in and alsa_out

Since we used subdevice 0,0 for playback and subdevice 0,1 for capture, remember that signals from these subdevices will be available by loopback to the corresponding subdevices, respectively 1,0 and 1,1 in this case. So the trick for jack is to use alsain and alsaout on the latter subdevices :) Brilliant ins't it ? :D

Let's do it from the terminal

# capture client
alsa_in -j cloop -dcloop
# playback client
alsa_out -j ploop -dploop 

I hope you start to see the underlying idea. Once these clients show up in the graph, when an ALSA app plays back to subdevice 0,0 (default ALSA device defined in our asounrdc), the signal will be available in subdevice 1,0, which alsa_in listens to. The "cloop" client we created can now be connected to the jack system output ports and o miracle, you will hear your ALSA app :)

In order to avoid the warning messages from alsa_in/out, you can add the relevant parameters, e.g. (my case):

 alsa_in -j cloop -dcloop -n 2 -p 256 -r 96000

On the other hand, if you connect a jack system input port to the "ploop" client created by alsa_out, the signal is sent to loopback subdevice 1,1 which will be looped back to subdevice 0,1. This subdevice is nothing but our ALSA capture device, defined in asoundrc :). So now you can record say your bass or guitar or voice (from your jack hardware) to an ALSA app that does not support jack. I tried skype, and it works just fine. You can also try the command line app called ecasound, which does support jack but also ALSA. It is a VERY convenient tool to have around (see further down).

The beauty of it is two-fold:

Create scripts to automate bridge initialization via QjackCtl

The creation of the "p/cloop" clients can be automated, together with their connection to jack system ports. Here is my script:

#!/bin/sh
# script loop2jack, located in /usr/local/bin

# loop client creation
/usr/bin/alsa_out -j ploop -dploop -q 1 2>&1 1> /dev/null &
/usr/bin/alsa_in -j  cloop -dcloop -q 1 2>&1 1> /dev/null &

# give it some time before connecting to system ports
sleep 1

# cloop ports -> jack output ports 
jack_connect cloop:capture_1 system:playback_1
jack_connect cloop:capture_2 system:playback_2


# system microphone (RME analog input 3) to "ploop" ports  
jack_connect system:capture_3 ploop:playback_1
jack_connect system:capture_3 ploop:playback_2

# done
exit 0

Note that I used -q 1 as an option to alsa_in/out. This has to do with the resampling quality. At 2.3ms latency, 96kHz s.r. on a 2 x 2.4 GHz dual core CPU system and using Jack2, I get a low CPU usage (1-2%) and the quality is reasonable. If you push it to 2, 3 or 4, the CPU will increase quite a lot at small buffering / latency

In qjackctl (which I use, YMMV), go to Options -> Execute after server startup and add

/usr/local/bin/loop2jack

OK, now that you have added all this, save the qjackctl config, quit and restart it. Start jack, you should see the ploop and cloop clients in the graph with the connections between the ports we chose in the loop2jack script.

Test it: open say lmms, load a demo project, play it :)

And voila! try skype, which you can record in ardour if you want (don't forget to connect your jack system mic directly to the ardour track which you had connected to the "cloop" client or you will miss recording what you are saying to the other person ;)

So this was a pure software solution and this has the benefit that all your jack input ports are available to the ploop jack client so that ALSA apps can record the audio coming from these jack ports via the looped-back device. Of course, the loop stuff has latency (the default dmix and dsnoop buffering is quite big), but who cares ? ... Well actually, I did care a little so I revisited some things and estimated the latency added by the Loopback device. I also tweaked a hybrid solution where the ALSA capture PCM is using a real hardware (onboard chip or extra soundcard). Just read below.

Alternative Setup: hardware and software based solution

As mentioned in my introduction, I happen to have an onboard chip (Intel HDA) but also a USB webcam with a built-in mic. It would be a shame not to use their recording capability in some way, especially since I tend to use skype from my DAW PC quite often.

Adding extra h/w inputs in asoundrc

So instead of using the Loopback device for the ALSA capture (all the stuff related to "ploop" in the previous asoundrc), I simply declared the extra h/w in the asoundrc. So I removed all the ploop stuff including the now useless Loopback subdevices used for ALSA capture and alsa_out, and added hw PCM devices on the Intel device and USB webcam.

# ------------------------------------------------------
# hardware 0,0 : used for ALSA playback
pcm.loophw00 {
  type hw
  card Loopback
  device 0
  subdevice 0
  format S32_LE
  rate 96000
}

# ------------------------------------------------------
# playback PCM device: using loopback subdevice 0,0
pcm.amix {
  type dmix
  ipc_key 219347
  slave {
    pcm loophw00
    period_size 4096
    periods 2
  }
}

# ------------------------------------------------------
# software volume
pcm.asoftvol {
  type softvol
  slave.pcm "amix"

  control { name PCM }

  min_dB -51.0
  max_dB   0.0
}

# ------------------------------------------------------
# for jack alsa_in: looped-back signal at other ends
pcm.cloop {
  type hw
  card Loopback
  device 1
  subdevice 0
  format S32_LE
  rate 96000
}

# ------------------------------------------------------
# hardware Intel: used for ALSA capture
pcm.intel {
  type hw
  card Intel
}

pcm.isnoop {
  type dsnoop
  ipc_key 219346
  slave.pcm "intel"
}

# ------------------------------------------------------
# hardware USB cam: used for ALSA capture
pcm.usb {
  type hw
  card U0x46d0x81b # name obtained from inspecting file '/proc/asound/cards'
}

# ------------------------------------------------------
# duplex device combining our PCM devices defined above
pcm.aduplex {
  type asym
  playback.pcm "asoftvol"
  capture.pcm "usb"
}

# ------------------------------------------------------
# default device

defaults.pcm.rate_converter "samplerate_best"

pcm.!default {
  type plug
  slave.pcm aduplex

  hint {
       show on
       description "ALSA Default"
  }
}

Testing the new ALSA capture

This one was easy to test. I made sure that my asoundrc default card used the "usb" pcm capture (see above). I then fired up skype, set it to use the "Default" device for everything. I fired QasMixer (a nice QT4 based mixer if you don't know it) and controlled the capture level of the USB webcam from there (I do not let skype control my levels). Then I tried the skype test call and made sure it recorded my voice. The webcam is by the way a Logitech Webcam C310 which I am satisfied with. Works out of the box in linux due to its class compliance with USB vid.

Note that the same thing can be done with the Intel HDA capture ("intel" pcm capture defined in the previous asoundrc) provided that you plug a mic to its input jack of course :)

Measuring the latency introduced by the Loopback device

For measuring the latency, I had to be able to provide both Jack and ALSA with a common audio source.

Playback only

First, I fired up jackd and alsa_in on "cloop" (just as before). Then, I used ecasound as the middle-man for allowing the measuring of the eventual delay in ardour (which I am comfortable with, you can of course use another jack enabled recording software if you want).

Here is the ecasound command line, very simple:

 ecasound -i jack -o alsa

Then in qjackctl, I connected a sound source like my microphone available at system:capture_3 to ecasound:playback_1/2.

In ardour, I created two tracks: one mono track accepting audio from the same system capture port, and a stereo track connected to the "cloop" client. Indeed, since ecasound outputs to the default ALSA device, the cloop client should have the audio by loopback. I then recorded something in ardour so both tracks contained data coming from the same audio source. I compared the resulting waveform and observed a delay of 120 ms when the dmix parameters are set to default.

Another way is to use the click sound ardour provides, instead of a microphone as mentioned above. Just connect the ardour click output ports to ecasound's input ports, and connect the click ports to one of the ardour tracks, the other one should still receive the cloop client data. If you fiddle with the dmix parameters in the .asoundrc, you will obtain various delays. It is therefore up to you to decide how the periodsize and buffersize params must be set.

A 120 ms delay is not bad at all considering the huge buffering dmix configures by default. But remember that dmix really sucks at small buffering so you have to choose a reasonably large one. I ended up using the following:

# ------------------------------------------------------
# playback PCM device: using loopback subdevice 0,0
pcm.amix {
  type dmix
  ipc_key 219347
  slave {
    pcm "loophw00"
    period_size 4096
    periods 2
  }
}

This setting gives me a final Loopback latency of \~ 35ms, while dmix does a good job without choking.

Capture and Playback

If you are using the complete software solution (ALSA playback and capture via cloop and ploop), then you can still use ecasound as an intermediate tool. Just fire it up in this way:

ecasound -i alsa -o alsa

In qjackctl, connect the ardour click ports to the "ploop" ports. This will allow ecasound to record the ardour click via the looped-back ploop audio. Then ecasound will output it to the default ALSA pcm playback which alsa_in collects via the cloop client.

In ardour, just like the setup above, have two tracks, one receiving the internal ardour click directly, the other connected to the cloop ports. Arm the tracks for recording, enable the click, activate the transport. You will see audio data in both tracks, one is delayed of course (the one connected to cloop). With my tuned asoundrc, I get an overall Loopback latency well below 100ms (approximately 75ms). It is not bad at all for the whole purpose of the Loopback bridge.

If low latency is a concern, don't use ALSA only apps, use jackified apps :D

Final word

I hope all this was clear enough. The idea behind this was to use a h/w capture device instead of the Loopback device. This reduces the role of the Loopback device to ALSA playback only, and removes the need of alsa_out, sparing some CPU and jack process cycles. At the moment, I am using my USB webcam for capture because I only need ALSA capture for skype. The Intel HDA is available as well but I don't really need it. It is connected to my patch panel though, so I can always use it if the need comes (unlikely).

Troubleshooting

If you have pulseaudio on you machine, better kill it, overwise it doesn't work.