How to Implement RMS-Based Audio Ducking in Meetily to Prevent System Audio from Drowning Out Microphone
TLDR: Meetily achieves RMS-based audio ducking by calculating the root-mean-square energy of microphone chunks in AudioMixer::mix and attenuating system audio to 60% volume (configurable via system_ducking) whenever the mic signal exceeds the SPEECH_THRESHOLD of 0.01, preventing background audio from overpowering speech.
Meetily is an open-source meeting assistant that captures parallel audio streams from both microphone and system sources. When recording meetings with shared media or background applications, implementing RMS-based audio ducking in Meetily ensures the microphone remains intelligible by automatically reducing system volume during speech. The ducking logic lives entirely within the Rust-based audio pipeline in the frontend/src-tauri/src/audio/ directory of the Zackriya-Solutions/meetily repository.
Understanding the Audio Pipeline Architecture
Meetily's audio processing relies on four interconnected components that manage buffering, mixing, and device-specific timing.
-
SourceBuffer: Located in
frontend/src-tauri/src/audio/ffmpeg_mixer.rs(lines 78-110), this struct holds time-stamped audio chunks for each input source and applies adaptive timeouts based on whether the device is wired or Bluetooth. -
AudioMixer: The core ducking implementation in
frontend/src-tauri/src/audio/ffmpeg_mixer.rs(lines 55-85). This struct performs RMS calculations and applies dynamic gain scaling whenadaptive_duckingis enabled. -
FFmpegAudioMixer: A façade that orchestrates two
SourceBufferinstances, callsAudioMixer::mix, and returns 50ms mixed windows. Defined infrontend/src-tauri/src/audio/ffmpeg_mixer.rs(lines 40-70). -
AudioPipeline: A higher-level manager in
frontend/src-tauri/src/audio/pipeline.rs(lines 45-90) that extracts windows and forwards them to the recorder. It falls back toProfessionalAudioMixerwhen adaptive ducking is disabled.
How RMS-Based Ducking Works
The ducking algorithm follows a five-step process that evaluates microphone energy and adjusts system gain in real-time:
-
Capture:
AudioCapturefeeds microphone and system audio into their respectiveSourceBufferinstances. -
Ready Check:
FFmpegAudioMixer::has_data_readyverifies both buffers contain sufficient data according to device-specific adaptive timeouts. -
RMS Calculation: Inside
AudioMixer::mix, the system computes the root-mean-square energy of the microphone chunk usingcalculate_rms. The code compares this against a calibrated threshold:
let mic_rms = calculate_rms(mic);
const SPEECH_THRESHOLD: f32 = 0.01; // calibrated for meetings
let is_speech = mic_rms > SPEECH_THRESHOLD;
This logic appears in frontend/src-tauri/src/audio/ffmpeg_mixer.rs at lines 88-95.
-
Dynamic Gain Application: When speech is detected, the system audio gain is set to the
system_duckingfactor (default 0.60, representing 40% reduction). Otherwise, system gain remains at 1.0 (full volume). -
Mixing and Clipping: Samples are mixed using the formula
mic * mic_ducking + system * system_gainand clamped to[-1.0, 1.0]to prevent digital clipping (lines 103-108 inffmpeg_mixer.rs).
Implementing the Ducking Logic
Basic Setup with FFmpegAudioMixer
To enable RMS-based ducking in your Meetily implementation, instantiate FFmpegAudioMixer with adaptive processing enabled. The following example configures the mixer for 48kHz audio with default ducking parameters:
use meetily::audio::ffmpeg_mixer::FFmpegAudioMixer;
use meetily::audio::device_detection::InputDeviceKind;
// Create the adaptive mixer (adaptive ducking enabled by default)
let mut mixer = FFmpegAudioMixer::new(
"Built‑in Mic".into(),
InputDeviceKind::Wired,
"BlackHole 2ch".into(),
InputDeviceKind::Wired,
48_000,
);
// Feed raw chunks as they arrive from capture callbacks
mixer.push_mic(mic_chunk);
mixer.push_system(sys_chunk);
// Retrieve mixed 50ms windows when both buffers have data
if let Some(mixed) = mixer.pop_mixed() {
// mixed is a Vec<f32> containing interleaved stereo samples
// Forward to Whisper transcription or file recorder
transcribe_audio(mixed);
}
The constructor is defined at FFmpegAudioMixer::new (lines 40-70) and the mixing retrieval at FFmpegAudioMixer::pop_mixed (lines 94-106).
Customizing Ducking Parameters
For fine-grained control over the ducking behavior, construct a custom AudioMixer struct with modified gain values. This example reduces system audio to 40% during speech instead of the default 60%:
use meetily::audio::ffmpeg_mixer::{AudioMixer, FFmpegAudioMixer};
use meetily::audio::device_detection::InputDeviceKind;
// Configure custom AudioMixer with aggressive ducking
let custom_mixer = AudioMixer {
mic_ducking: 1.0, // maintain full microphone volume
system_ducking: 0.4, // reduce system to 40% during speech
adaptive_ducking: true, // enable RMS-based speech detection
};
// Construct FFmpegAudioMixer with custom configuration
let mut mixer = FFmpegAudioMixer {
mic_buffer: SourceBuffer::new("Mic".into(), InputDeviceKind::Wired, 48_000),
system_buffer: SourceBuffer::new("System".into(), InputDeviceKind::Wired, 48_000),
mixer: custom_mixer,
sample_rate: 48_000,
mixing_window_samples: 2400, // 50ms at 48kHz
windows_mixed: 0,
};
// Process audio as normal - custom system_ducking applies automatically
mixer.push_mic(microphone_data);
mixer.push_system(system_data);
The AudioMixer struct fields are defined at lines 57-63 in ffmpeg_mixer.rs, with the default constructor implementation at lines 68-74.
Configuring Speech Detection Thresholds
The sensitivity of the ducking trigger depends on two configurable constants in the AudioMixer implementation:
-
SPEECH_THRESHOLD: Set to
0.01f32by default (lines 88-95). Lower values trigger ducking more aggressively for quiet speech; higher values reduce false positives from background noise. -
system_ducking: Controls the attenuation depth. Values range from 0.0 (complete mute) to 1.0 (no ducking). The default 0.60 provides a balance where system audio remains audible but subordinate to microphone input.
To adjust the threshold for different acoustic environments, modify the constant in frontend/src-tauri/src/audio/ffmpeg_mixer.rs before compilation, or extend the AudioMixer struct to accept runtime threshold configuration.
Summary
Meetily's RMS-based audio ducking provides automatic gain control that preserves microphone clarity during system audio playback:
- The
AudioMixerinfrontend/src-tauri/src/audio/ffmpeg_mixer.rscalculates microphone RMS energy and attenuates system audio by 40% whenSPEECH_THRESHOLDis exceeded. - FFmpegAudioMixer orchestrates the dual-buffer architecture, delivering 50ms mixed windows at 48kHz sample rate.
- Ducking behavior is controlled via the
system_duckingparameter (0.0-1.0) and theadaptive_duckingboolean flag. - The algorithm prevents clipping by clamping mixed output to
[-1.0, 1.0]using the formulamic * mic_ducking + system * system_gain.
Frequently Asked Questions
How does Meetily detect when to apply audio ducking?
Meetily detects speech by calculating the root-mean-square (RMS) energy of each microphone audio chunk in AudioMixer::mix. When the calculated mic_rms value exceeds the SPEECH_THRESHOLD constant of 0.01, the system identifies active speech and triggers the ducking mechanism to reduce system audio volume according to the system_ducking parameter.
Can I disable RMS-based ducking and use simple mixing instead?
Yes. Set the adaptive_ducking parameter to false when constructing the AudioMixer, or use the ProfessionalAudioMixer fallback located in frontend/src-tauri/src/audio/pipeline.rs. When disabled, the mixer sums both audio sources at full volume without RMS analysis or dynamic gain adjustments.
What is the latency impact of the RMS calculation?
The RMS calculation adds negligible latency because it operates on 50ms windows (2400 samples at 48kHz) using a single pass through the buffer. The FFmpegAudioMixer maintains separate SourceBuffer instances for microphone and system audio, ensuring that timing synchronization occurs before mixing rather than during the RMS computation itself.
How do I tune the ducking for very quiet speakers?
Decrease the SPEECH_THRESHOLD constant in frontend/src-tauri/src/audio/ffmpeg_mixer.rs below the default 0.01 value, or increase the system_ducking attenuation to 0.3 or 0.2 for more aggressive background suppression. For extremely quiet environments, you may also need to adjust your microphone gain upstream before it reaches the Meetily pipeline, as the RMS calculation depends on sufficient signal-to-noise ratio in the input buffer.
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 →