Measure Audio Latency
This example shows how to measure the latency of an audio device. The example uses audioLatencyMeasurementExampleApp which in turn uses audioPlayerRecorder along with a test signal and cross correlation to determine latency. To avoid disk access intereference, the test signal is loaded into a dsp.AsyncBuffer object first, and frames are streamed from that object through the audio device.
In general terms, latency is defined as the time from when the audio signal enters a system until it exits. In a digital audio processing chain, there are multiple parameters that cause latency:
- Hardware (including A/D and D/A conversion)
- Audio drivers that communicate with the system's sound card
- Sampling rate
- Samples per frame (buffer size)
- Algorithmic latency (e.g. delay introduced by a filter or audio effect)
This example shows how to measure round trip latency. That is, the latency incurred when playing audio through a device, looping back the audio with a physical loopback cable, and recording the loopback audio with the same audio device. In order to compute latency for your own audio device, you need to connect the audio out and audio in ports using a loopback cable.
Roundtrip latency does not break down the measurement between output latency and input latency. It measures only the combined effect of the two. Also, most practical applications will not use a loopback setup. Typically the processing chain consists of recording audio, processing it, and playing the processed audio. However, the latency involved should be the same either way provided the other factors (frame size, sampling rate, algorithm latency) don't change.
Smaller frame sizes and higher sampling rates reduce the roundtrip latency. However, the tradeoff is a higher chance of dropouts occurring (overruns/underruns).
In addition to potentially increasing latency, the amount of processing involved in the audio algorithm can also cause dropouts.
Measuring Latency with audioLatencyMeasurementExampleApp.m
The function audioLatencyMeasurementExampleApp computes roundtrip latency in milliseconds for a given setup. Overruns and underruns are also presented. If the overruns/underruns are not zero, the results are likely invalid. For example:
audioLatencyMeasurementExampleApp('SamplesPerFrame',64,'SampleRate',48e3) % The measurements in this example were done on macOS. For most % measurements, a Steinberg UR22 external USB device was used. For the % measurements with custom I/O channels, an RME Fireface UFX+ device was % used. This RME device has lower latency than the Steinberg device for a % given sample rate/frame size combination. Measurements on Windows using % ASIO drivers should result in similar values.
Trial(s) done for frameSize 64. ans = 1×5 table SamplesPerFrame SampleRate_kHz Latency_ms Overruns Underruns _______________ ______________ __________ ________ _________ 64 48 8.3125 0 0
Some Tips When Measuring Latency
Real-time processing on a general purpose operating system is only possible if you minimize other tasks being performed by the computer. It is recommended to:
- Close all other programs
- Ensure no underruns/overruns occur
- Use a large enough buffer size (SamplesPerFrame) to ensure consistent dropout-free behavior
- Ensure your hardware settings (buffer size, sampling rate) match the inputs to measureLatency
On Windows, you can use the asiosettings function to launch the dialog to control the hardware settings. On macOS, you should launch the Audio MIDI Setup.
When using ASIO (or CoreAudio with Mac OS), the latency measurements are consistent as long as no dropouts occur. For small buffer sizes, it is possible to get a clean measurement in one instance and dropouts the next. The Ntrials option can be used to ensure consistent dropout behavior when measuring latency. For example, to perform 3 measurements, use:
Trial(s) done for frameSize 96. ans = 3×5 table SamplesPerFrame SampleRate_kHz Latency_ms Overruns Underruns _______________ ______________ __________ ________ _________ 96 48 10.312 0 0 96 48 10.312 0 0 96 48 10.312 0 0
Measurements For Different Buffer Sizes
On macOS, it is also possible to try different frame sizes without changing the hardware settings. To make this convenient, you can specify a vector of SamplesPerFrame:
BufferSizes = [64;96;128]; t = audioLatencyMeasurementExampleApp('SamplesPerFrame',BufferSizes) % Notice that for every sample increment in the buffer size, the additional % latency is 3*SamplesPerFrameIncrement/SampleRate (macOS only).
Trial(s) done for frameSize 64. Trial(s) done for frameSize 96. Trial(s) done for frameSize 128. t = 3×5 table SamplesPerFrame SampleRate_kHz Latency_ms Overruns Underruns _______________ ______________ __________ ________ _________ 64 48 8.3125 0 0 96 48 10.312 0 0 128 48 12.312 0 0
Specifically, in the previous example, the increment is
3*[128-96, 96-64]/48e3 % In addition, notice that the actual buffering latency is also determined % by 3*SamplesPerFrame/SampleRate. Subtracting this value from the measured % latency gives a measure of the latency introduced by the device (combined % effect of A/D conversion, D/A conversion, and drivers). The numbers above % indicate about 4.3125 ms latency due to device-specific factors. t.Latency_ms - 3*BufferSizes/48
ans = 0.0020 0.0020 ans = 4.3125 4.3125 4.3125
Specifying Custom Input/Output Channels
The measurements performed so far assume that channel #1 is used for both input and output. If your device has a loopback cable connected to other channels, you can specify them using the IOChannels option to measureLatency. This is specified as a 2-element vector, corresponding to the input and output channels to be used (the measurement is always on a mono signal). For example for an RME Fireface UFX+:
audioLatencyMeasurementExampleApp('SamplesPerFrame',[32 64 96],... 'SampleRate',96e3,'Device','Fireface UFX+ (23767940)',... 'IOChannels',[1 3])
Trial(s) done for frameSize 32. Trial(s) done for frameSize 64. Trial(s) done for frameSize 96. ans = 3×5 table SamplesPerFrame SampleRate_kHz Latency_ms Overruns Underruns _______________ ______________ __________ ________ _________ 32 96 2.6458 0 32 64 96 3.6458 0 0 96 96 4.6458 0 0
The measurements so far have not included algorithm latency. Therefore, they represent the minimal roundtrip latency that can be achieved for a given device, buffer size, and sampling rate. You can add a linear phase FIR filter the processing chain to verify that the latency measurements are as expected. Moreover, it provides a way of verifying robustness of the real-time audio processing under a given workload. For example,
L = 961; Fs = 48e3; audioLatencyMeasurementExampleApp('SamplesPerFrame',128,... 'SampleRate',Fs,'FilterLength',L,'Ntrials',3) % The latency introduced by the filter is given by the filter's % group-delay. GroupDelay = (L-1)/2/Fs % The group delay accounts for the 10 ms of additional latency when using a % 961-tap linear-phase FIR filter vs. the minimal achievable latency.
Trial(s) done for frameSize 128. ans = 3×6 table SamplesPerFrame SampleRate_kHz FilterLength Latency_ms Overruns Underruns _______________ ______________ ____________ __________ ________ _________ 128 48 961 22.312 0 0 128 48 961 22.312 0 0 128 48 961 22.312 0 0 GroupDelay = 0.0100
Plotting the Original and Recorded Signal
%The latency measurements are determined by cross-correlating a source %audio signal with a delayed version of the signal that results after %loopback through the audio device. You can use the Plot option in %measureLatency to plot the original and delayed signal along with the %cross correlation: audioLatencyMeasurementExampleApp('SamplesPerFrame',128,'Plot',true) % If the optional FIR filtering is used, the waveforms are not affected % because the filter used has a broader bandwidth than the test audio % signal.
Trial(s) done for frameSize 128. Plotting... ans = 1×5 table SamplesPerFrame SampleRate_kHz Latency_ms Overruns Underruns _______________ ______________ __________ ________ _________ 128 48 12.312 0 0