I had a conversation with old friend, occasional colleague, future Audio Anecdotes contributor(?), Craig Hansen-Sturm recently about the application of lockless data structures to implement robust, low-latency audio systems.
I can imagine why Craig, freshly hired by venerable sequencer maker Cakewalk, is interested in low-latency audio, but I was surprised to refer him to a list of AudioAnecdotes articles that riff on a simple producer consumer lockless FIFO and its implications for API design, audio latency, sample accurate synchronization, testing for synchronization and dropped samples, and more (linked to google versions of articles where possible):
- Introduction to the Ring Buffer FIFO Queue (AAv2)
- Wrapped I/O (AAv2)
- RampTest: Quantifying Audio Performance (AAv1)
- Synchronization Demystified: An Introduction to Synchronization Terms and Concepts (AAv3)
- Synchronization in Film: Birth of the Talkie (AAv3)
- Sample Accurate Synchronization Using Pipelines: Put a Sample In and We Know When It Will Come Out (AAv3)
- Dynamic Synchronization: Drifting Into Sync (AAv3)
Or my (not so succinct) summary:
1) A FIFO acts as impedance match between a producer and consumer (that is why it is called a buffer (think chemistry)).
2) A carefully designed RingBuffer implementation eliminates the requirement for a mutex thereby allowing the producer and consumer to efficiently access the FIFO without requiring expensive overhead of system calls (thereby allowing these operations to occur much more frequently, potentially enabling much lower latency). Additionally without the mutex the possibility of glitch-producing priority inversions, or worse, outright deadlocks are eliminated.
3) Latency may be dynamically controlled via high and low water marks (essentially controlling how much of the available buffer is actually used).
4) RingBuffer access may be wrapped by elegant POSIX-like blocking-io (those Bell Labs folk left us a wonderful legacy). This model is both easy for developers to comprehend (less code, fewer bugs and gotcha’s) AND is straightforward to implement via mechanisms like selectable semaphores which transparently work with the OS scheduler to automatically temporarily boost the priority of an unblocked process (say when a FIFO fills past the low water mark thereby unblocking a reading consumer).
5) Issues like underflow, fan-in, and fan-out, may be managed in a well understood and elegant way. For instance quiescent underflow allows intentional starvation to silence and future injection of a synchronized signal later (analogous to the silent leader on an audio tape).
6) Pipelines with understood and constant latency allow for sample-accurate synchronization between streams without requiring any hard-real-time problems to be solved.
This got longer than I hoped. Read the actual articles and let me know what you think!