Does using Mixxx with JACK give you zero latency?

Date Author Daniel Schürmann Tag jack, alsa, sound, latency, pipewire

In regular intervals, we discuss how much latency the JACK Audio Connection Kit introduces when used in Mixxx. That is one of the Sound APIs that Mixxx supports on Linux, and it's a layer on top of the Advances Linux Sound Architecture (ALSA).

Unlike analog devices, digital audio devices process audio in time slices of samples stored in memory buffers. These are passed through the various layers. The latency of a digital audio device depends on the number and size of those buffers. The aim is to minimize the latency, so that the audible result of pressing a button or turning a knob in Mixxx is produced without noticeable delay, in order to allow DJing by ear.

The JACK FAQ state that:

There is NO extra latency caused by using JACK for audio input and output. When we say none, we mean absolutely zero.

This is true on its own, because JACK uses the buffer configured for ALSA directly to mix the audio sources together. ALSA has a second buffer with the same length that is used to feed these samples into the hardware. That's all.

However, in the case of Mixxx two more buffers are used by JACK for syncing and mixing the buffers of the patch field. It allows the client applications to use the full CPU time of each buffer cycle.

This is not a problem of JACK per se, but of the way JACK support was implemented in Mixxx.

When using the ALSA API directly Mixxx does what JACK does: It uses the ALSA buffer directly, which doesn't add any latency.

This can be confirmed by recording the own sound via a mic and measure the round trip latency. For my test, I have used the internal speaker, microphone and ALC298 Analog sound. For separating the signals in one stereo stream, I have enabled the Balance effect to move deck 1 fully to the right channel and the mic fully to the left. The test track is a 440 Hz sine wave generated by Audacity. Then I have cued the track into the pre-roll, started recording and pressed play.

These are the resulting recordings visualized in Audacity:

Screenshot of audacity showing the round trip latency

The upper stream is the JACK case. The left channel is the recorded master 440 Hz sine wave and the right channel is the mic input.

JACK is configured with a 1024 frames buffer and reports a latency of 46.4 ms for the sum of two buffers. The round trip latency is 95 ms (driver + ALSA + JACK async mode + duplex stream = 4 buffers).

The lower stream is the ALSA case. Mixxx is configured with the same single buffer of 1024 frames = 23.2 ms. The round trip latency is 49 ms (driver + ALSA = 2 buffers).

Not in the picture is the ALSA pulse device. It runs at a latency of 104 ms (driver + ALSA + 2 x pulse + ALSA = 5 buffers) PipeWire on Fedora 34 has by default the same latency as JACK in this test.

With this picture we can verify that Mixxx actually has the same buffer size in both cases. When pressing pause, it fades the signal out over one buffer length which is equal in both cases. The peaks in the recorded right channel is the sound of the mouse click. You can only barely see the recorded sine wave.

Screenshot of audacity showing the fade out

For reference, I have done the same test using jack_iodelay

3114.842 frames     70.631 ms total roundtrip latency
extra loopback latency: 42 frames
use 21 for the backend arguments -I and -O

The result is 70 ms (driver + ALSA + JACK async mode = 3 buffers). The duplex cycle is omitted here. This is one buffer more than a native ALSA implementation.

To verify that the duplex cycle is not introduced by Mixxx or the PortAudio abstraction layer used by Mixxx, I have routed the jack_iodelay signal through Mixxx.

Jack patch field showing the jack_iodelay Mixxx loop

2048.000 frames     46.440 ms total roundtrip latency
extra loopback latency: 2047 frames
use 1023 for the backend arguments -I and -O

This is the minimum we can expect, one buffer is needed for jack_iodelay and one for Mixxx. The result can be confirmed with the same setup using jack_latent_client which just passes the input to the output.

If we connect the output of Mixxx to the input, an extra buffer is used for some reason. This can be also confirmed with jack_latent_client and the same feedback loop.

Jack feedback loop

The upper stream shows the Mixxx results and the lower stream shows the jack_latent_client results.

In addition, most distros use JACK with its default "Server Asynchronous Mode" which introduce one buffer of extra latency (--async-latency) when accessing the sound card. In Ubuntu Hirsute 21.4, QJackCtl exposes a "Use server synchronous mode" checkbox in the sound card preferences. It is grayed out by default but becomes active if "Enable JACK D-Bus interface" is checked as well. In this case the JACK mixing is done after Mixxx in the same time interval.

Conclusion

For now, we recommend using Mixxx with the ALSA backends even if you are running JACK. The same applies to PipeWire as Mixxx uses JACK protocol to connect to it.

It should be also noted that JACK can't deal well with two or more sound cards. Mixxx can do the required clock sync using the ALSA API and this with no extra latency as long the underlying driver allows it. For details refer the Mixxx manual.

Pipewire will become default on most distros and we need to find out what is the best setup for using it with Mixxx. Do you have interest to help? Get in contact with us at Zulip

Daniel Schürmann GitHub profile Discourse profile
Mixxx Core Developer
More from this Author

Comments