The sound subsystem was the odd one in qemu. Configuration using environment variables like QEMU_AUDIO_DRV instead of command line switches. That time is over now. For qemu 4.2 we finally completed merging full -audiodev support, written by Zoltán Kővágó. Read on to learn what has changed.

Using the environment variables continues to work for now. But this configuration method is deprecated and will be dropped at some point in the future. Maybe in qemu 5.1 (earliest release the qemu deprecation policy would allow), maybe we'll leave the compatibility code in for a few more releases to allow a longer transition period given this is a rather fundamental change.

Creating audio backends

The new -audiodev command line switch creates an audio backend, simliar to -netdev for network backends or -blockdev for block device backends. All sound backend configuration is done using -audiodev parameters.

The simplest case is to just specify the backend and assign an id to refer to it, like this:

qemu -audiodev spice,id=snd0

Some backends have additional configuration options. For pulseaudio for example it is possible to specify server hostname:

qemu -audiodev pa,id=snd0,server=localhost

Stream parameters can be specified separately for in (record) and out (playback). There are some parameters which are common for all backends (frequency, channels, ...) and backend-specific parameters like the pulseaudio stream name (visible in mixer applications like pavucontrol) or the alsa device:

qemu -audiodev pa,id=snd0,out.frequency=48000,out.stream-name=foobar
qemu -audiodev alsa,id=snd0,out.dev=default

Buffer sizes are specified in microseconds everywhere. So configuring alsa with a buffer for 10 milliseconds of sound data and 4 periods (2.5 miliseconds each) works this way:

qemu -audiodev alsa,id=snd0,out.buffer-length=10000,out.period-length=2500

The -audiodev switch accepts json too (simliar to -blockdev), so the last example can also be specified this way:

qemu -audiodev "{
    'driver' : 'alsa',
    'id' : 'snd0',
    'out' : {
      'buffer-length' : 10000,
      'period-length' : 2500
    }
}"

Consult the audio qapi schema for all available config options (including documentation).

Using audio backends

That is the simple part. All sound devices got a new audiodev parameter to link the device with an audio backend, using the id assigned to the backend:

qemu -device usb-audio,audiodev=snd0

Done.

Note: Right now the audiodev= parameter is optional, for backward compatibility reasons. The parameter will become mandatory though when we drop the code for the deprecated evironment variable configuration method. So I strongly recommend to use the parameter explicitly everywhere, to be prepared for the future.

Multiple audio backends and 5:1 surround support

Finally a more complex example to showcast what the new sound configuration allows for:

qemu \
  -audiodev pa,id=hda,out.mixing-engine=off \
  -audiodev pa,id=usb,out.mixing-engine=off \
  -device intel-hda -device hda-output,audiodev=hda \
  -device qemu-xhci -device usb-audio,audiodev=usb,multi=on

Walkthrough:

  • We can have multiple backends, by simply specifying -audiodev multiple times on the command line and assigning different ids. That can be useful even in case of two identical backends. With pulseaudio each backend is a separate stream and can be routed to different output devices on the host (using a pulse mixer app like pavucontrol).
  • Using the backend ids we assign one backend to the HDA device and the other to the USB device.
  • qemu has an internal audio mixer, for mixing audio streams from multiple devices into one output stream. The internal mixer can also do resampling and format conversion if needed. With the pulseaudio backend we don't need it as the pulseaudio daemon can handle all this for us. Also the internal mixer is limited to mono and stereo, it can't handle multichannel (5:1) sound data. So we use mixing-engine=off to turn off the internal mixer.
  • The USB audio device has multichannel (5:1) support. This is disabled by default though, the multi=on parameter turns it on.

Enjoy!