baseLatency and outputLatency Are Not Your Round Trip: Building a Wired Loopback Tap

baseLatency and outputLatency Are Not Your Round Trip: Building a Wired Loopback Tap

If you have ever tried to sync a web audio graph to something outside the browser — a video frame, a MIDI clock, a hardware synth, a second machine — you have probably reached for AudioContext.baseLatency or AudioContext.outputLatency and treated the number as the delay between “I scheduled a sample” and “a human heard it.” That is not what those properties measure. They are narrow, browser-reported estimates of specific boundaries inside the chain. The distance between those boundaries and the listener is exactly the part you cannot read from JavaScript, and it is usually the part that matters.

This article is a protocol, not a results table. I am not going to hand you millisecond figures for a named laptop and browser, because I do not have a first-party measurement log to back them. What I can do is lay out what the two properties actually promise, why a wired loopback tap is the right instrument for the rest of the chain, and how to record the measurement so another developer can rerun it and compare rather than trust it.

What the two properties actually describe

The definitions are worth reading slowly, because the boundaries are precise.

baseLatency is “the number of seconds of processing latency incurred by the AudioContext passing an audio buffer from the AudioDestinationNode — i.e., the end of the audio graph — into the host system’s audio subsystem ready for playing” (MDN, AudioContext.baseLatency). It stops at the host subsystem boundary. It says nothing about the OS mixer, the driver, the DAC, the cable, the ADC, the input path, or the browser’s input buffering.

outputLatency is “an estimation of the output latency of the current audio context … the time, in seconds, between the browser passing an audio buffer out of an audio graph over to the host system’s audio subsystem to play, and the time at which the first sample in the buffer is actually processed by the audio output device” (MDN, AudioContext.outputLatency). It reaches the output device, but it is explicitly an estimation, and MDN notes it “varies depending on the platform and the available hardware.”

Neither property is a round trip. Neither includes the input side. Neither includes a cable. If you are synchronizing against a physical tap — a click fed back into an input, a hardware device echoing a pulse — the number you need is the one that spans the whole loop, and the browser does not give it to you.

Two related facts shape the measurement. First, latencyHint is a request, not a contract: “You can request a certain latency during construction time with the latencyHint option, but the browser may ignore the option” (same MDN page). Second, the default render quantum is 128 frames (W3C Web Audio API), which is small relative to the buffer periods you will actually observe at the device boundary. The render quantum is not the buffer size you are measuring against.

getOutputTimestamp() is the closest the API comes to a clock that knows about the output device. It returns a contextTime for “the sample frame currently being rendered by the audio output device” and a performanceTime that is “an estimation of the moment when the sample frame corresponding to the stored contextTime value was rendered by the audio output device” (MDN, getOutputTimestamp()). That is useful for aligning visual updates to audio, but it is still an estimate anchored to the output device, not a measurement of what came back through a cable.

Why a wired loopback, not an acoustic one

Acoustic loopback tests — play a click through the speakers, catch it with the microphone — are convenient and they measure something real: the full perceptual chain including air travel and microphone placement. Published tools use this approach. The weblatencytest project plays an MLS signal through browser output and captures it through the microphone input, and its README recommends “a stable acoustic or wired loopback setup with clearly audible output and minimal background noise” (gilpanal/weblatencytest). A browser-based tester describes its measurement as “the round trip through the output stack, the air, the input stack and the browser” (toolchamp.app audio latency tester).

For engineering comparison, though, the air path is a liability. Speaker response, room reflections, microphone placement and distance all enter the number, and none of them are properties of your audio stack. A wired loopback — output jack or interface output into an input on the same or a second interface — removes the speaker, the room, the microphone and the air. What remains is a chain you can describe precisely in a versioned log: output buffer, OS mixer, driver, DAC, cable, ADC, input path, browser input buffer. That is the chain you are trying to characterize when you ask whether your sync is good enough.

This is a recommendation, not a finding. The acoustic method answers “what does a listener experience in this room.” The wired method answers “what does this software and hardware stack contribute.” Pick the one that matches your question, and say which one you used.

A loopback protocol you can rerun

The following is a proposed method, not a tested result from this site. It is assembled from the documented behavior of the two properties and from the method notes of the tools cited above. Treat it as a starting point to adapt and record, not as a validated procedure.

1. Fix the signal

Use a deterministic stimulus. Two families work well:

  • Impulse or short click. Easy to detect with a threshold on the input buffer. Sensitive to noise floor and to the threshold you choose.
  • MLS or other broadband sequence with cross-correlation. More robust in noisy captures. The weblatencytest project uses an MLS signal and computes cross-correlation to estimate delay, and it reports a correlation-peak-to-residual ratio in dB as a trust indicator, with a fixed threshold of +18 dB described as usually indicating a reliable estimate (gilpanal/weblatencytest).

Whichever you choose, record the exact waveform, its length, and the detection method. A threshold-based click and a cross-correlated MLS are not interchangeable numbers.

2. Schedule and timestamp on the same clock

Schedule the stimulus with AudioBufferSourceNode.start() or an oscillator at a known contextTime, and capture the input with an AudioWorklet or an AnalyserNode whose buffer you read on a known cadence. Timestamp the capture with performance.now() at the moment you read the buffer, and keep the mapping between contextTime and performanceTime explicit. getOutputTimestamp() gives you a pair to anchor that mapping, but remember it is an estimate.

3. Repeat, and report the distribution

One round is not a measurement. Audio buffers are discrete: a stimulus that starts just after a buffer boundary waits for the next one, so the measured latency quantizes to the buffer period. The toolchamp tester states the arithmetic plainly: “with a 512-sample buffer at 48 kHz that is about 10.7 ms of legitimate variation” (toolchamp.app). That is not noise; it is the buffer grid.

Run enough rounds to see the grid, and report the median, the spread, and the buffer size and sample rate that produced them. A mean alone hides the quantization and invites false precision.

4. Record the browser’s numbers alongside, not instead

Log baseLatency, outputLatency (if present), sampleRate, and the latencyHint you requested. Do not expect them to agree with the tap. The gap is the informative quantity: it is the part of the chain the browser cannot see. If outputLatency is missing, record that too — the toolchamp tester notes that outputLatency is optional in the specification and that Safari in particular does not implement it (toolchamp.app). A missing field is data, not a failure.

5. Calibrate the converter path if you can

A wired loopback still includes the DAC and ADC of your interface. If you have a second interface or a known-latency converter, measure the converter path separately and subtract it, or at minimum state that it is included. Cable delay is negligible at audio frequencies; converter latency is not. If you cannot separate it, say so in the log.

What the numbers mean when you have them

You will end up with two columns that are expected to differ. The browser column answers “what does the browser think its own output path costs.” The tap column answers “what does the whole loop cost on this rig, with this cable, at this sample rate, with this buffer.” Neither is wrong. They answer different questions.

Published experiments show why the tap column is worth having. In the weblatencytest results, round-trip latency varied substantially across browsers and systems, and Chromium-based browsers showed standard deviations around 8 ms on some Ubuntu and Windows systems (gilpanal/weblatencytest). That variability is not visible in baseLatency or outputLatency. If your sync budget is tight, the spread is the number that decides whether you need a compensation strategy.

One caution: a loopback tap measures the electrical and software chain. It does not measure perceptual latency — what a listener experiences as the delay between a visual event and a sound. Do not present a tap figure as a perceptual figure. They are related, but the mapping depends on the listener, the content, and the display path.

A reproducibility checklist

If you publish a latency figure, publish the conditions with it. At minimum:

  • Browser and exact version.
  • OS and version, and the audio backend (ALSA, PulseAudio, PipeWire, CoreAudio, WASAPI).
  • Audio interface or cable used, and whether the converter path is included.
  • Sample rate and the buffer size you observed, not just the one you requested.
  • Render quantum size if you can read it.
  • Number of rounds, and the detection method and threshold.
  • Median and spread, not a single figure.
  • The browser-reported baseLatency and outputLatency values, or their absence.

That list is the difference between a number and a measurement. A reader who can rerun your protocol can compare; a reader who cannot has to trust you. For a versioned experiment log aimed at developers who refuse vendor lock-in, rerunnability is the whole point.

FAQ

Can I just use outputLatency for sync?

You can use it as one input, but it is an estimation of the output path only. It does not include the input side, and it does not include anything past the output device. For sync against a physical tap, measure the tap.

Why does my measured latency jump around by about 10 ms?

Buffer quantization. A stimulus that starts just after a buffer boundary waits for the next one. At 48 kHz with a 512-sample buffer, that is about 10.7 ms of legitimate variation (toolchamp.app). Report the distribution, not one round.

What if outputLatency is undefined?

It is optional in the specification, and the toolchamp tester notes Safari in particular does not implement it (toolchamp.app). Record the absence and rely on the tap for the full-chain figure.

Is a wired loopback better than an acoustic one?

It is better for characterizing the software and hardware stack, because it removes the speaker, room, microphone and air from the chain. It is not better for answering “what does a listener hear in this room.” Choose by question, and say which you used.

Does latencyHint guarantee a lower latency?

No. It is a request, and the browser may ignore it (MDN). Measure the result rather than assuming the hint was honored.