A workbench for building and measuring glitch-free, low-latency real-time audio on Windows.
Latency and dropouts are the hard part of real-time audio, and they are usually discussed by feel rather than measured. This project does the opposite. It runs a full capture, process, and playback path as the device under test, and wraps it in a harness that reports round-trip latency and xrun (dropout) counts objectively, across backends and buffer sizes, so tuning is driven by numbers instead of guesswork.
It targets WASAPI (shared and exclusive mode) and ASIO. The audio path and the DSP chain are written to be genuinely real-time-safe, and the repo documents why each decision was made, since that discipline is the whole game in audio callbacks.
Scope note: this is a measurement lab and reference engine, not a finished product. It exists to prove a design is glitch-free at a given latency and to make the trade-offs visible.
flowchart LR
In[Capture callback\nWASAPI / ASIO] -- lock-free ring --> Proc[DSP chain\nreal-time-safe]
Proc -- lock-free ring --> Out[Playback callback\nWASAPI / ASIO]
Proc -. counters .-> Meter[Latency + xrun harness]
Meter --> Report[CSV / console report]
- Capture and playback run on the audio thread through WASAPI (shared or exclusive) or ASIO.
- Hand-off between threads is a lock-free single-producer/single-consumer ring buffer. No allocation, no locks, and no system calls happen inside the audio callback.
- Processing is a pluggable DSP chain (biquad filters, gain, mixing, resampling) that runs within the callback's time budget.
- Measurement records round-trip latency and counts underruns/overruns, then sweeps buffer sizes and sample rates to show where the design stays clean and where it breaks.
- Round-trip latency in frames and milliseconds, measured by loopback: emit a click, capture it back, and compute the sample offset. No reliance on a driver's self-reported figure.
- Xruns per hour under a controlled processing load, per backend and buffer size.
- Callback headroom: time spent in the callback versus the time available, so you can see how close to the edge a given buffer size runs.
- The audio callback never allocates, never locks, and never blocks. Everything it needs is preallocated.
- Cross-thread data moves through a wait-free ring buffer, so the UI and worker threads cannot stall the audio thread.
- DSP work is bounded and measured, so a buffer is always filled before its deadline.
- The callback takes a raw function pointer and a
void*, not astd::function, so there is no captured state to allocate or indirect through on the hot path.
What the engine is built to achieve, and what a bench run measures against. These are the
specified targets, not results from a run: the numbers that belong in a results table can only
come from a real machine with real hardware, and the Status list below tracks that.
| Property | Target |
|---|---|
| Allocation in the callback | None. Every buffer preallocated before start() |
| Locks in the callback | None. Cross-thread hand-off is a wait-free SPSC ring |
| Ring buffer ordering | Release on producer publish, acquire on consumer read |
| WASAPI shared | Works at the mix-format rate; latency bounded by the shared engine period |
| WASAPI exclusive | Device period drives the buffer; handles the buffer-alignment retry |
| ASIO | Same backend interface; built only when the SDK is present locally |
| Latency measurement | Loopback cross-correlation against the emitted signal, not a driver self-report |
| Xruns | Counted lock-free from the callback, reported per backend and buffer size |
| Callback headroom | Measured as time-in-callback over time-available, per block |
| Sweep output | CSV plus console, containing only what was actually measured |
- WASAPI shared-mode capture and playback with lock-free ring buffer
- WASAPI exclusive-mode low-latency path
- ASIO backend
- Pluggable real-time-safe DSP chain (biquad, gain, resample)
- Loopback round-trip latency measurement
- Xrun counting and buffer-size sweep
- Bench run on real hardware, measured against the targets above
C++17, built with CMake. ASIO support requires the Steinberg ASIO SDK (not redistributed here;
drop it in third_party/asio locally). WASAPI needs no extra SDK on Windows.
cmake -B build && cmake --build build --config Release
build/Release/rt-audio-latency-lab --backend wasapi-exclusive --buffer 64 --measure
build/Release/rt-audio-latency-lab --sweep --out results.csv
MIT.