How Meetily Synchronizes Microphone and System Audio Using Ring Buffers
Meetily aligns asynchronous microphone and system audio streams by buffering them in separate ring buffers, extracting fixed-size windows when either buffer fills, and zero-padding the shorter stream to maintain perfect synchronization before mixing.
Meetily is an open-source meeting transcription tool that captures both microphone input and system audio simultaneously. Because these two hardware sources operate on independent clocks and suffer from network or OS-level jitter, the application implements a sophisticated ring-buffer-based synchronizer in its Rust audio pipeline. This system ensures that Whisper receives perfectly aligned audio chunks regardless of timing drift between capture devices.
Ring Buffer Architecture in pipeline.rs
The synchronization core is the AudioMixerRingBuffer struct defined in [frontend/src-tauri/src/audio/pipeline.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs#L16-L24). This dual-channel buffer maintains separate FIFO queues for each audio source:
struct AudioMixerRingBuffer {
mic_buffer: VecDeque<f32>,
system_buffer: VecDeque<f32>,
window_size_samples: usize, // e.g., 50 ms worth of samples
max_buffer_size: usize, // safety limit (≈ 400 ms)
}
window_size_samples is calculated based on the device sample rate and a target 50 ms mixing window in the new constructor (pipeline.rs#L26-34). The max_buffer_size caps each buffer at approximately 400 ms to absorb temporary jitter—particularly important for macOS Core Audio latency fluctuations—while preventing unbounded memory growth.
Feeding Samples from Multiple Sources
When audio chunks arrive from the capture threads, the pipeline invokes add_samples (pipeline.rs#L49-64) to append them to the appropriate buffer:
match device_type {
DeviceType::Microphone => self.mic_buffer.extend(samples),
DeviceType::System => self.system_buffer.extend(samples),
}
This method performs periodic buffer health logging and enforces the max_buffer_size limit by trimming excess samples. If overflow occurs, the system emits diagnostic warnings via warn and error macros (pipeline.rs#L66-76), allowing developers to detect capture device anomalies without crashing the recording session.
Window-Based Synchronization Logic
The pipeline determines readiness for mixing through the can_mix method (pipeline.rs#L87-90):
fn can_mix(&self) -> bool {
self.mic_buffer.len() >= self.window_size_samples ||
self.system_buffer.len() >= self.window_size_samples
}
When can_mix returns true, extract_window (pipeline.rs#L92-140) pulls exactly window_size_samples from each channel. If one buffer lacks sufficient data, the remaining samples are zero-padded (filled with silence) rather than repeating the last sample. This approach eliminates audible artifacts like clicking or echoing when one source lags behind the other.
The Mixing Stage
Aligned windows pass to ProfessionalAudioMixer::mix_window (pipeline.rs#L54-84), which combines the streams into a single output buffer:
- System audio receives a configurable scaling factor (currently 1.0) to balance against microphone levels.
- Soft scaling prevents hard clipping by checking the absolute sum of samples and applying gain reduction when the mixed signal approaches the -1.0 to +1.0 bounds (
pipeline.rs#L77-84).
The resulting mixed PCM buffer is then forwarded to RecordingSaver for disk persistence and/or passed to the Whisper transcription engine.
End-to-End Pipeline Flow
- Capture –
AudioCapturespawns concurrent threads for microphone ([capture/microphone.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/capture/microphone.rs)) and system audio ([capture/system.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/capture/system.rs)). - Buffer – Each chunk calls
ring_buffer.add_samples(device_type, samples)to feed the ring buffers. - Sync – The recording manager polls
can_mix()to detect when a full window is available. - Extract –
extract_window()returns aligned(mic_window, system_window)tuples, zero-padding the shorter stream. - Mix –
ProfessionalAudioMixercombines the windows with clipping protection. - Emit – The final buffer routes to
recording_saver.rsfor file writing and transcription processing.
This architecture automatically corrects clock drift: if the microphone produces samples faster than system audio, excess mic samples remain in the ring buffer for the next window, while missing system samples are replaced with silence, maintaining constant output latency.
Summary
- Dual-ring architecture – Separate
VecDeque<f32>buffers isolate microphone and system audio streams. - Deterministic windows – Fixed 50 ms mixing intervals ensure uniform chunk sizes for Whisper transcription.
- Zero-padding strategy – Silence injection prevents artifacts when sources drift out of sync.
- Soft clipping protection – Dynamic scaling in
ProfessionalAudioMixerpreserves audio quality during loud passages. - Jitter resilience – 400 ms
max_buffer_sizeaccommodates OS-level latency fluctuations without data loss.
Frequently Asked Questions
What happens if one audio source stops producing samples?
If either the microphone or system audio stream stalls, extract_window continues to output mixed audio by zero-padding the missing source. This prevents the transcription pipeline from blocking and eliminates repetitive artifacts that would occur if the last valid sample were repeated.
Why does Meetily use ring buffers instead of simple vectors?
Ring buffers (implemented via VecDeque) provide O(1) push and pop operations from both ends, allowing efficient FIFO management. According to the Meetily source code, this is critical for real-time performance when handling two asynchronous streams with different hardware latency characteristics.
How does the system handle sample rate mismatches between devices?
The AudioMixerRingBuffer assumes both streams operate at the same sample rate specified during instantiation (typically 48 kHz). The capture modules in capture/microphone.rs and capture/system.rs resample hardware input to match this target rate before calling add_samples, ensuring the ring buffer receives homogenous PCM data.
Where is the mixing logic located in the codebase?
The core mixing implementation resides in frontend/src-tauri/src/audio/pipeline.rs, specifically within the ProfessionalAudioMixer struct and its mix_window method. The orchestration logic that coordinates capture threads and triggers mixing is found in [recording_manager.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/recording_manager.rs).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →