QEMU Native JACK Audio Support

Over the last few days, I have been working on a jack audiodev for Qemu which I have just submitted upstream is now officially part of QEMU slated for 5.1 (https://github.com/qemu/qemu/blob/master/audio/jackaudio.c). In my testing, this fixes all stuttering/desync audio issues with all emulated Qemu audio devices in Windows and Linux.

As QEMU has introduced a new method of specifying the audiodev it’s now very simple to use this.

For example, an Intel HDA ICH9 device:

  -audiodev jack,id=ad0 \
  -device ich9-intel-hda \
  -device hda-duplex,audiodev=ad0

The audio dev accepts some additional parameters:

parameter type default description
server-name string “default” The JACK server instance to connect to
client-name string the VM’s name The client name to use in JACK. This will have “out-” or “in-” prepended to it automatically based on the audio direction.
connect-ports regex If specified, automatically connect to ports matching the supplied regular expression
start-server boolean false Set to true to autostart a jack instance if one is not present.
exact-name boolean false Use the exact client name requested otherwise JACK generates a unique one, if needed.

For example:

  -audiodev jack,id=ad0,in.client-name=Windows,out.client-name=Windows

Note, for best performance the jack client thread needs realtime priority and will attempt to obtain it if possible, however, the QEMU -runas can interfere with this. The requirements to make this work has been left as an exercise to the user (personally I just run Qemu as root :stuck_out_tongue: )

Enjoy and have fun :smiley:

EDIT: Updated link to version 4 of the patch. Just cosmetic code changes, documentation changes, and parameters have gone from using underscores (_) to hyphens (-) as per the QEMU standard practice.

EDIT: New version 5 patch, adds the ability to auto-connect to ports based on a regular expression match, as well as recovery if the jack server is restarted.

EDIT: New version 6 patch, fixes incorrect transport logic, audio stop no longer stops the JACK transport.

NOTE: QEMU has an internal resampler that we need to use, it is enabled by default but it’s designed to only handle sample rates up to 65.35kHz, do not expect this to work well at anything higher. This is not a limitation of this JACK audio dev, but QEMU itself. As such jackd can not run at anything that exceeds this limit either. That said, the virtual audio devices are only 16-bit anyway, so if you are trying to get professional audio quality from QEMU then you need to go a different route (audio vfio passthrough) for now (I intend to correct this :slight_smile: )

17 Likes

Is there any recommended environment variable? Like setting up buffer size etc?

QEMU sound configuration via environment variables is deprecated as of Qemu 4.1.

Buffer sizes for Jack are configured at launch time on the Jackd server, not in the client. I am running a 128 buffer size with 2 periods. Note that this is very hardware dependent and you may have to play with the buffer size to find something suitable for your system.

There are heaps of guides on how to setup the jack server, so I will not go into it here, just do a little research :slight_smile:

I’m not getting any of the -audiodev parameters to work, namely:

qemu-system-x86_64: -audiodev jack,id=sound0,start-server=true: Parameter 'start-server' is unexpected

Am I missing something?

Please be sure you’re using version 4 of the patch, it has been linked above. RedHat’s convention is to use hyphens instead of underscores and this had to be adjusted which the latest version of the patch does, and what this information above references.

I’ve double confirmed I’m using the correct version. It extends to all parameters - none are recognized.

For reference, I’m building this against the 5.0.0 upstream release.

I am very sorry, I forgot that the settings are specified per in/out, ie.

-audiodev jack,id=sound0,out.start-server=true,in.start-server=true

1 Like

My VM setup fully migrated over to using JACK now. With the latest version of the patch (v5) it now supports auto connection to ports based on a regular expression match. It also watches for new port registrations and connects to them when they become available so that the load order doesn’t matter. And finally, it will recover if the audio server (jackd) is restarted, which neither PA or ALSA can do.

1 Like

Link to patch updated.

I just realized there is another very cool usage of this which I am now using also. I wanted to bring the Windows audio from my VM into my Linux VM as a seperate stream for recording and mixing. So I added another hda device to my Linux VM and set it to connect directly to the Windows output.

On the sufrace this seems obvious, but because JACK is configured to use shared memory, this means that this audio data is being shared directly between the two VMs via shared buffers. The latency of doing this should be far better then any DAC loopback type device could ever be.

Edit: actually thinking more, one could add another virtual audio device to the Windows guest as the non-default device, then tell your program to use it as the output device, avoiding windows ever mixing in audio from other sources, like system events/sounds, etc. This would ensure a clean recording no matter what is going on with the system.

2 Likes

This is amazing!

Thank you for all that you do for the community!

@gnif

Amazing work, as always. I had to do some small modifications to have the sound stable on my system on your v9 patch (keep in mind I am not that well versed in C so I am wary of doing other modifications and introducing stupid bugs):

Around line 506 of the patch you verify if the buffersize is smaller than 512 and adjust it to be 512, but 30 lines below (on 534) you redefine the buffersize to whatever we acquire from the server.

My sound works quite well in all jack applications except QEMU, which required me to redo your buffer checking.

I also had to increase the period buffer from 2 to 3.

I have 2 questions: any particular reason why you increase the buffersize then you redefine it and don’t check if it is that minimum size again?

Could we introduce a few more parameters? One defining the minimum buffer size and the period (I thought we would be able to acquire the period information from jack server, but I don’t know enough about the protocol).

Tried to do this (run two independent interfaces) and my VM is crashing when booting up

jack: I: Jack: JackPosixThread::Kill
jack: I: Jack: jack_client_close
jack: I: Jack: JackClient::Close ref = 8
jack: I: Jack: JackClient::Deactivate
jack: I: Jack: JackSocketClientChannel::Stop
jack: I: Jack: JackPosixThread::Kill
jack: I: Jack: JackClientSocket::Close
jack: I: Jack: JackClientSocket::Close
jack: I: Jack: JackLibClient::~JackLibClient
jack: I: Jack: JackShmReadWritePtr1::~JackShmReadWritePtr1 8
jack: I: Jack: Succeeded in unlocking 426 byte memory area
jack: I: Jack: JackLibGlobals Destroy 3922dbb0
jack: I: Jack: ~JackLibGlobals
jack: I: Jack: no message buffer overruns
jack: I: Jack: JackPosixThread::Stop
jack: I: Jack: JackPosixThread::ThreadHandler : exit
jack: I: Jack: JackShmReadWritePtr::~JackShmReadWritePtr 1
jack: I: Jack: Succeeded in unlocking 1187 byte memory area
jack: I: Jack: JackShmReadWritePtr::~JackShmReadWritePtr 0
jack: I: Jack: Succeeded in unlocking 82280346 byte memory area
jack: I: Jack: jack_client_close res = 0

Just deleted my entire post… sorry I completely misunderstood the issue… I think you’re right, I have made an error and at some point changed the ordering of things and missed the buffer size check.

Nothing I can do without a backtrace from a debug build of QEMU.

Just figured out what it was, I was adding too many parameters to the hda device (to follow up what libvirt was doing). Removed them and it just started working.

(=

Note that v9 is officially part of QEMU now also:

Thank you so much for you work on this! It was just in time. I’m able to stream my Fedora VM through OBS now.

1 Like

@gnif, I just want to thank you as well for adding such a wonderful feature. It makes working with VM audio pleasurable instead of unbearable.

I took the opportunity to revamp my entire audio setup and now have full live dsp and routing for all my VM’s and native sound with Jack similar to your demonstrated setup.

Some videos are much nicer to watch with a little live EQ to mask recording setup issues.

Keep up the awesome work. One day I will have to figure out how to run looking-glass with my setup (dual monitor, same VC, looking-glass to mirror only one/secondary) but haven’t had the time to mess with it and see if it’s possible.

1 Like

Just a few notes for libvirt users:

  1. To get access to the JACK socket, qemu needs to run as the user who runs jackd (which is typically the logged in user). I’ve tried mocking around with JACK promiscuous mode without much success (if anyone is successful, please drop a note). So if you have anything other than user = <jackd_user> in /etc/libvirt/qemu.conf, you’ll need the user to be the same as the jackd process.

  2. By default, libvirt enables the qemu sandbox with all syscall groups in deny. JACK tries to elevate priority and qemu kills it. To get around this you have to unfortunately disable qemu sandboxing completely in libvirt by setting
    seccomp_sandbox = 0 in /etc/libvirt/qemu.conf