Recording Path vs Transcription Path in Meetily's Audio Pipeline
The recording path captures the full mixed audio (microphone + system) to disk as a WAV file, while the transcription path filters that same mixed audio through Voice Activity Detection (VAD) and sends only speech segments to Whisper at 16 kHz for live captioning.
Meetily's audio processing engine, located in the frontend/src-tauri/src/audio/pipeline.rs file of the Zackriya-Solutions/meetily repository, implements a dual-path architecture that processes the same captured audio for two distinct purposes. Understanding the difference between these paths reveals how the application simultaneously produces high-fidelity meeting recordings and real-time transcripts without redundant processing.
Core Architecture of Meetily's Audio Pipeline
The pipeline begins with AudioCapture grabbing synchronized streams from both the microphone and system audio. After optional preprocessing (resampling, noise suppression, and loudness normalization), samples accumulate in an AudioMixerRingBuffer. When a 50 ms window is available, extract_window returns synchronized buffers that feed both downstream paths.
The critical divergence occurs inside AudioPipeline::run after the ProfessionalAudioMixer blends the two audio sources. At this point, the mixed buffer travels through two separate channels: one for persistence and one for speech recognition.
The Recording Path: Capturing the Complete Audio Mix
Data Flow and File Destination
In the recording path, the pipeline forwards the complete mixed window to recording_sender_for_mixed. This occurs immediately after mixing, preserving the full audio experience exactly as the user heard it.
The code in frontend/src-tauri/src/audio/pipeline.rs (lines 68-78) wraps the mixed buffer in an AudioChunk struct:
if let Some(ref rec_sender) = self.recording_sender_for_mixed {
let rec_chunk = AudioChunk {
data: mixed.clone(),
sample_rate: self.sample_rate, // Original rate (e.g., 48000 Hz)
timestamp: chunk.timestamp,
chunk_id: self.chunk_id_counter,
device_type: DeviceType::Microphone,
};
let _ = rec_sender.send(rec_chunk);
}
The RecordingSaver (defined in recording_saver.rs) receives these chunks and writes them sequentially to a .wav file on disk. Because this path receives every sample from both audio sources, the final recording contains the complete meeting audio without gaps or filtering.
Configuration and Initialization
The recording path is activated when AudioPipelineManager::start receives a recording_sender option. Lines 94-97 of pipeline.rs establish this connection:
// In AudioPipelineManager::start
pipeline.recording_sender_for_mixed = recording_sender;
If no sender is provided, the recording path remains inactive, allowing the pipeline to run transcription-only mode.
The Transcription Path: Isolating Speech for Whisper
VAD Filtering and Resampling
The transcription path processes the same mixed buffer through ContinuousVadProcessor before sending data to the speech recognition engine. This path discards silence and non-speech segments to optimize Whisper's performance and reduce processing overhead.
In frontend/src-tauri/src/audio/pipeline.rs (lines 85-108), the transcription logic follows the recording logic:
// Transcription path (VAD → Whisper)
if let Ok(segments) = self.vad_processor.process_audio(&mixed) {
for seg in segments {
let trans_chunk = AudioChunk {
data: seg.samples,
sample_rate: 16_000, // Down-sampled for Whisper
timestamp: seg.start_timestamp_ms / 1_000.0,
chunk_id: self.chunk_id_counter,
device_type: DeviceType::Microphone,
};
let _ = self.transcription_sender.send(trans_chunk);
}
}
Notice that the transcription path creates new AudioChunk objects with 16 kHz sample rate regardless of the original capture rate. This down-sampling ensures compatibility with Whisper's expected input format while the recording path preserves the original sample rate for archival quality.
Whisper Integration
Chunks emitted on transcription_sender travel to the Whisper Engine in frontend/src-tauri/src/audio/transcription/engine.rs. The engine converts these speech-only segments into text and emits Tauri events (transcript-update) that the frontend UI consumes for live captions.
Key Differences Between the Two Paths
| Recording Path | Transcription Path |
|---|---|
| Goal: Archive complete mixed audio for playback | Goal: Feed speech-only segments to Whisper for text conversion |
| Output: Full WAV file with mic + system audio | Output: Live text stream via Tauri events |
| Sample Rate: Preserved at original capture rate (e.g., 48000 Hz) | Sample Rate: Hard-coded to 16000 Hz for Whisper compatibility |
| Filtering: None; receives all audio including silence | Filtering: VAD removes non-speech segments before transmission |
Destination: RecordingSaver writes to disk |
Destination: Whisper Engine processes in memory |
How the Two Paths Work Together
The coordinated flow ensures both paths receive identical source material while serving different consumption patterns:
- Capture: Raw streams enter through
microphone.rsandsystem.rswith synchronized timestamps - Mixing:
ProfessionalAudioMixer::mix_windowblends buffers without aggressive ducking - Recording Branch: The mixed buffer clones immediately to
recording_sender_for_mixed - Transcription Branch: The same mixed buffer passes through VAD, creating speech-only chunks down-sampled to 16 kHz
- Parallel Processing: Disk I/O and speech recognition occur simultaneously on separate threads
This architecture guarantees that pauses, background noise, and cross-talk appear in the final recording, while only intelligible speech triggers the transcription API—optimizing both accuracy and cost.
Summary
- Recording path in
frontend/src-tauri/src/audio/pipeline.rssends complete mixedAudioChunkdata toRecordingSaverfor WAV file creation at the original sample rate. - Transcription path processes identical mixed audio through
ContinuousVadProcessor, sending only speech segments down-sampled to 16 kHz to the Whisper Engine. - Both paths originate from the same 50 ms window extraction in
AudioPipeline::run, ensuring temporal alignment between recorded audio and generated transcripts. - The separation allows Meetily to archive full-fidelity audio while minimizing Whisper processing costs by filtering silence.
Frequently Asked Questions
Why does the transcription path use 16 kHz while the recording path preserves the original sample rate?
Whisper models are trained and optimized for 16 kHz audio input. The transcription path resamples audio to match this expectation, ensuring maximum transcription accuracy while reducing computational load. The recording path preserves the original rate (commonly 48000 Hz) to maintain broadcast-quality audio for playback and post-processing.
What happens to audio segments that the VAD classifies as non-speech?
Non-speech segments (silence, typing, background noise) are discarded by ContinuousVadProcessor and never reach transcription_sender. These segments still flow through the recording path, so the archived WAV file captures the complete meeting audio including all pauses and environmental sounds.
Can the recording path operate independently without transcription enabled?
Yes. The recording_sender_for_mixed field is an Option in the AudioPipeline struct. If AudioPipelineManager::start receives None for the recording sender, only the transcription path executes. Conversely, providing a recording sender without configuring transcription would require initializing the VAD processor, though typically both paths run simultaneously in production.
Where does the actual file writing occur in the recording path?
The RecordingSaver component defined in frontend/src-tauri/src/audio/recording_saver.rs handles the final disk writes. It receives AudioChunk objects from the recording_sender_for_mixed channel and appends them to the output WAV file, managing headers and finalization when the recording stops.
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 →