If your audio stream stutters, the cause is almost always a buffer underrun (consumer ran out of data) or overrun (producer overwhelmed consumer). Solving both at once requires a ring buffer with explicit drift correction — not a generic queue.

Advertisement

Why not a regular queue?

Standard FIFO queues grow unbounded on overrun and crash on underrun. Audio needs fixed-size circular buffers with read and write pointers that wrap. The buffer size sets your latency floor (e.g., 5ms at 48kHz = 240 samples).

The ring buffer pattern

pub struct RingBuf {
    data: Vec, write: AtomicUsize, read: AtomicUsize,
    cap: usize,
}
impl RingBuf {
    pub fn write(&self, samples: &[f32]) -> usize {
        // lock-free SPSC, returns bytes actually written
    }
    pub fn read(&self, out: &mut [f32]) -> usize {
        // ditto
    }
    pub fn available_read(&self) -> usize { /* ... */ 0 }
}
Advertisement

Clock drift correction

Your microphone clock and your speaker clock are not synchronized down to the last nanosecond. Over 10 minutes, a 50 ppm drift produces a 30 ms offset — enough to cause audible glitches. Use a small resampler running at ratio ~1.000005x to correct slow drift without sample drops.

Underrun recovery

When the consumer asks for samples and the buffer is empty, you must emit SOMETHING — silence (audible click), repeat the last frame (audible buzz), or fade-out + fade-in (least artifact). Pick fade for production; log the underrun for ops to alert on.

Choosing buffer size

Use caseBufferUnderrun risk
Pro audio (DAW)~5 msHigh
Game voice~20 msMedium
VoIP~60 msLow
Streaming playback1-3 sVery low
Ring buffer + drift correction + fade-on-underrun. Anything else and you will get audible glitches in production.