How to Optimize Meetily's Audio Pipeline for Low-Latency Recording
Reduce the mixing window to 50ms, reuse a persistent Rubato resampler, compile with --release to strip debug logging, tune the VAD redemption time to 200ms, and implement forced buffer flushing to achieve sub-100ms end-to-end latency from microphone capture to transcription.
Meetily's meeting assistant relies on a real-time audio pipeline built in Rust that processes microphone and system audio through a VAD-driven ring-buffer architecture. To optimize Meetily's audio pipeline for low-latency recording, you must adjust buffer sizes, resampling strategies, and voice activity detection parameters defined in the core audio processing files.
Core Optimization Strategies
1. Minimize the Ring-Buffer Mixing Window
The AudioMixerRingBuffer accumulates samples until a fixed window is ready for processing. In frontend/src-tauri/src/audio/pipeline.rs (lines 16-36), the default mixing window is set to 600ms, which creates unnecessary delay before audio chunks can be mixed and forwarded.
Reduce the window_ms constant to 50ms to minimize the time before a chunk can be mixed. The max buffer of 400ms absorbs device jitter while allowing the smaller processing window:
// In AudioMixerRingBuffer::new (pipeline.rs:16-36)
let window_ms = 50.0; // Reduced from 600ms for ultra-low latency
2. Persist the Rubato Resampler Across Chunks
Audio resampling can introduce significant overhead if performed with temporary allocations. Meetily uses a persistent SincFixedIn resampler from the Rubato crate that processes fixed-size 512-sample chunks. According to the source code in pipeline.rs (lines 24-66), reusing a single resampler instance prevents the "energy amplification" bug and eliminates per-chunk allocation overhead.
Ensure the resampler is created once per device in AudioCapture::new and reused throughout the session:
// Ensure persistent resampler is enabled (pipeline.rs:24-66)
let resampler = if needs_resampling {
// Keep the same instance across all chunks
Some(SincFixedIn::<f32>::new(
TARGET_SAMPLE_RATE as f64 / sample_rate as f64,
2.0,
params,
RESAMPLER_CHUNK_SIZE,
1,
)?)
} else { None };
3. Strip Hot-Path Logging in Production
Debug logging in the hot path can block the real-time thread with I/O overhead. In pipeline.rs (lines 86-115), Meetily uses a perf_debug! macro that expands to nothing in release builds, and summary logs are throttled to emit only every 200 chunks or 60 seconds.
Always build the Tauri frontend in release mode to eliminate these debug statements entirely:
# Build with --release so perf_debug! disappears
cargo build --release
4. Reduce VAD Redemption Time
The VAD (Voice Activity Detection) processor determines when speech segments end. By default, ContinuousVadProcessor is instantiated with a redemption_time of 400ms in pipeline.rs (line 27), meaning the system waits 400ms of silence before finalizing a speech segment.
Reducing this value to 200ms enables faster speech-end detection, allowing the pipeline to deliver segments to Whisper the moment speech stops rather than waiting for a timer-based threshold:
// Shorten VAD redemption time (pipeline.rs:27)
let redemption_time = 200; // Reduced from 400ms for faster cut-off
5. Implement Force-Flush on Shutdown
Without proper flushing, buffered audio can remain stuck in the pipeline when recording stops, causing shutdown latencies exceeding 30 seconds. The pipeline handles special "flush" chunks identified by chunk_id == u64::MAX to force an immediate VAD flush, as implemented in pipeline.rs (lines 70-88).
When stopping a meeting, call AudioPipelineManager::force_flush_and_stop() (lines 28-73) to guarantee the pipeline empties its buffers instantly, reducing shutdown latency to under 100ms:
// Flush immediately when stopping recording
pipeline_manager.force_flush_and_stop().await?;
Key Source Files for Low-Latency Tuning
The following files in the frontend/src-tauri/src/audio/ directory define the low-latency audio path:
pipeline.rs– Core mixing logic, ring-buffer management (AudioMixerRingBuffer), persistent resampler implementation, VAD loop, and flush handling (lines 16-115).audio_processing.rs– Helper functions for mono conversion, high-pass filtering, RNNoise integration, and loudness normalization.recording_manager.rs– Orchestrates pipeline start/stop and connects the mixed-audio recorder to the output.devices/– Device discovery and platform-specific audio APIs for testing latency across different operating systems.vad.rs– Implementation ofContinuousVadProcessorused for real-time speech segmentation.
Summary
- Shrink the mixing window from 600ms to 50ms in
AudioMixerRingBufferto reduce pre-processing delay. - Reuse a single
SincFixedInresampler across all chunks to avoid allocation overhead and energy amplification bugs. - Build with
--releaseto eliminateperf_debug!macros and throttled logging from the hot path. - Tune VAD redemption time from 400ms to 200ms for faster speech-segment delivery.
- Call
force_flush_and_stop()to prevent 30-second shutdown delays by immediately flushing remaining buffers.
Frequently Asked Questions
What is the default mixing window size in Meetily?
The default mixing window is 600ms as defined in pipeline.rs (line 28), but the architecture supports reducing this to 50ms without breaking the ring-buffer logic. The 400ms maximum buffer size provides sufficient headroom for device jitter while the smaller window enables faster processing.
Why does reusing the resampler reduce latency?
Creating a new resampler for each audio chunk causes memory allocation overhead and a known "energy amplification" bug. By reusing a persistent SincFixedIn instance created in AudioCapture::new (lines 24-66), the pipeline maintains a deterministic processing path with no per-chunk allocation, keeping the CPU cache warm and predictable.
How do I completely disable debug logging in the audio pipeline?
The pipeline uses conditional compilation through the perf_debug! macro. When you build with cargo build --release, these macros expand to nothing, completely removing debug logging from the hot path. Additionally, summary logs are automatically throttled to emit only every 200 chunks or 60 seconds to minimize I/O blocking.
What causes the 30-second shutdown delay in Meetily?
The delay occurs when the VAD buffer retains unprocessed audio samples waiting for silence detection to finalize segments. By implementing the force-flush mechanism using chunk_id == u64::MAX (lines 70-88) and calling AudioPipelineManager::force_flush_and_stop(), you force immediate VAD finalization, reducing shutdown latency from over 30 seconds to under 100 milliseconds.
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 →