Lock-Free Ring Buffer (HeapRb)
The buffering system usesringbuf::HeapRb<f32> as a lock-free FIFO queue between audio and network threads.
Design Overview
Initialization
Smart Device-Aware Sizing
The buffer size is automatically adjusted based on device type (audio.rs:437-466):
-
WASAPI Loopback (8000ms):
- More timing unpredictability
- Affected by CPU power management
- WiFi jitter on laptops
- Adaptive range: 4000-12000ms
-
Standard Input (5000ms):
- More predictable timing
- Direct hardware access
- WiFi tolerance
- Adaptive range: 2000-6000ms
Lock-Free Operations
Producer (Audio Thread)
- Non-blocking:
push_slice()never waits - Overflow behavior: Returns number of samples written
- Performance: O(1) time complexity
- Thread: Runs in cpal’s audio callback (real-time constraints)
Consumer (Network Thread)
- Non-blocking: Returns immediately
- Underflow behavior: Fills buffer with silence (zeros)
- Performance: O(1) time complexity
- Thread: Network thread (lower priority)
Why Mutex on Producer?
The producer is wrapped inArc<Mutex<...>> (audio.rs:1095):
- Is uncontended (single audio thread)
- Allows sharing producer across different closures
- Doesn’t block (lock is always available)
- Alternative would be separate producer instances per format (less clean)
Adaptive Buffer Sizing
Jitter-Based Resizing
Every 10 seconds, the system checks jitter and adjusts target buffer size (audio.rs:892-955):
Adaptive Ranges by Device Type
Jitter-to-Buffer Mapping
Current Limitation
The adaptive logic tracks target size but doesn’t physically resize the ring buffer:Jitter Handling
Sources of Jitter
-
Network Jitter:
- WiFi interference
- Router bufferbloat
- Competing traffic
-
OS Scheduler:
- Thread preemption
- CPU frequency scaling
- Background processes
-
Hardware:
- USB audio interfaces (variable latency)
- Laptop power management
- Thermal throttling
Buffer as Jitter Absorber
The ring buffer acts as a time-based cushion:High Water Mark (Drain Mode)
When buffer fills beyond threshold, the system enters drain mode (audio.rs:606-612):
Prefill Gate (v1.8.1)
Cold Start Problem
Without prefill:Implementation
- Balances startup delay and stability
- Absorbs TCP handshake latency
- Small enough to be imperceptible to user
- Large enough to prevent initial stutter
- Works equally well on Windows, Linux, macOS
Prefill + High Water Mark Relationship
Buffer Health Metrics
Calculation
1.0: Empty (consuming faster than producing)0.5: Half full (balanced)0.0: Full (producing faster than consuming)
Buffer Usage in Quality Score
- Network is slower than audio production
- Risk of overflow (dropped samples)
- Should increase buffer size or reduce chunk size
Overflow vs. Underflow
Overflow (Buffer Full)
Cause: Audio arrives faster than network can send Symptom: “Buffer overflow: Dropped X samples” warnings Effect: Audio gaps (dropped samples) Solutions:- Increase ring buffer size
- Increase chunk size (send larger batches)
- Check network congestion
- Enable adaptive buffering
Underflow (Buffer Empty)
Cause: Network sends faster than audio arrives Symptom: “Starvation: Generating silence chunk” debug logs Effect: Silence inserted (zeros) Solutions:- Increase prefill duration
- Decrease chunk size (smaller batches)
- Check audio device configuration
- Verify sample rate matches device
Memory Characteristics
Allocation
- Location: Heap (not stack)
- Type: Contiguous array of f32
- Lifetime: Owned by stream, dropped when stream stops
Size Examples
| Sample Rate | Duration | Samples | Memory |
|---|---|---|---|
| 44.1 kHz | 2000ms | 176,400 | 0.67 MB |
| 48 kHz | 2000ms | 192,000 | 0.73 MB |
| 48 kHz | 4000ms | 384,000 | 1.46 MB |
| 48 kHz | 8000ms | 768,000 | 2.93 MB |
| 48 kHz | 12000ms | 1,152,000 | 4.39 MB |
samples * 4 bytes (f32) / 1024 / 1024
Cache Efficiency
- Sequential writes: Producer writes forward (cache-friendly)
- Sequential reads: Consumer reads forward (cache-friendly)
- Wraparound: HeapRb handles circular logic internally
- False sharing: Avoided (separate head/tail cache lines)
Comparison: Lock-Free vs. Mutex-Based
HeapRb (Lock-Free)
- Wait-free for producer (critical for audio)
- No priority inversion
- Deterministic latency
- No kernel involvement
Hypothetical Mutex-Based
- Producer may block (audio dropouts!)
- Priority inversion risk
- Variable latency
- Kernel involvement (futex syscalls)
Future Enhancements
- Hot-swappable resizing: Atomic buffer replacement
- Metrics: Overflow/underflow counters exposed to frontend
- Variable chunk size: Dynamic adjustment based on jitter
- Multiple buffers: Separate buffers per network connection