KVM audio with ALSA loopback

As of version 2.3.0, the Kernel-based Virtual Machine (KVM) has the ability to emulate several audio devices:

~ $ qemu-system-x86_64 -soundhw help
Valid sound card names (comma separated):
sb16        Creative Sound Blaster 16
es1370      ENSONIQ AudioPCI ES1370
ac97        Intel 82801AA AC97 Audio
adlib       Yamaha YM3812 (OPL2)
gus         Gravis Ultrasound GF1
cs4231a     CS4231A
hda         Intel HD Audio
pcspk       PC speaker

On the host end, available drivers include ALSA, OSS and WAV. The latter two being not so useful for practical audio on modern systems, ALSA is the only real option. The problem is, KVM does not support virtual ALSA devices. It requires exclusive access to the hardware ALSA device, which of course leaves the host system without audio. If you need the virtual machine for some audio applications, it would be hugely practical to be able to somehow mix guest audio with host audio. This can be achieved using the ALSA loopback device.

Setting up the loopback device

First load the loopback kernel module snd-aloop normally using modprobe or init scripts or whatever suits you: modprobe snd-aloop. You now have access to the hardware loopback device. In your .asoundrc or asound.conf files put the following:

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

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

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

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

The last number identifies a loop device, the second last number indicates what end of the loop is in question. In reality, an ALSA loopback device has no "in" or "out" end, it works both ways. I think it's just easier to think it as a directional device. Therefore I named the devices as ins and outs. DAC and ADC stand for digital-analog conversion and analog-digital conversion respectively. I also like to think the devices as close to real physical ones as possible, again I just think it's easier. So when looking at the device pcm.loop_vm_dac_in for example, I think it as an output device (DAC) where I should be feeding audio (IN) in the virtual guest.

KVM options

I use a script to start up my KVM virtual machines. The script looks something like this:

audio=1
if test "X$1" == "Xaudio" || test "$audio" == 1
then
 dac="hw:Loopback,0,0" # loop_vm_dac_in
 adc="hw:Loopback,1,1" # loop_vm_adc_out
 soundhw="-soundhw hda"
else
 dac=""
 adc=""
 soundhw=""
fi

/bin/env QEMU_ALSA_DAC_DEV=$dac QEMU_ALSA_ADC_DEV=$adc qemu-system-x86_64 $soundhw -daemonize \ ...

Now the guest system should have a hardware ALSA device with exclusive access, but the host system audio works as usual. Next, we connect our host audio to the guest audio.

Connecting the host audio with the guest audio

I use either ALSA utilities directly or JACK server to listen and record stuff, depending on how small latencies I require. I have two scripts for these purposes. The first one connects using aplay and arecord:

arecord -r 44100 -c 2 -f S16_LE -D loop_vm_dac_out | aplay &
arecord -r 44100 -c 2 -f S16_LE -D julia_ain_mix_44100 | aplay -D loop_vm_adc_in

and the second one using JACK by creating two JACK devices:

alsa_in -j "VM output" -dloop_vm_dac_out &
alsa_out -j "VM input" -dloop_vm_adc_in

The julia_ain_mix_44100 is just a virtual ALSA device I use to mix incoming audio. The output of the first script looks like this:

Recording WAVE 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
Recording WAVE 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
Playing WAVE 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
Playing WAVE 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo

So, whatever goes into loop_vm_dac_in comes out of loop_vm_dac_out, and is recorded with CD quality using arecord and played back immediately using aplay. The same works the other way around, so whatever my julia_ain_mix_44100 device captures on the host system, it is recorded and played back to the loop_vm_adc_in device, whose other end loop_vm_adc_out is the input device in the guest system. With this setup I have used Skype (back in the days it used to crash my system somehow, so I had to use a virtual machine just in case) and MuseScore successfully on a Linux guest.

Remarks

It is worth noting that modern Windows guests do not work well with the QEMU audio devices. 32-bit Windows 7 (and newer?) has support for the AC97 audio device according to the docs but you're out of luck with 64-bit guests. Intel HD Audio is well-supported driver-wise, 64-bit Windows 7 and Windows 10 detect the HDA device automatically, but sound is heavily distorted and quite unusable.

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