How to Implement Real-Time Audio Level Monitoring with RMS in Meetily
Meetily provides a built-in RMS-based audio monitoring system that captures audio streams via CPAL, calculates root-mean-square and peak levels every 100ms, and emits normalized level data to the frontend through Tauri events.
Meetily (Zackriya-Solutions/meetily) implements real-time audio level monitoring directly in its Tauri backend using Rust. The system leverages the CPAL audio library to capture raw samples from selected devices, computes RMS and peak values for each audio frame, and streams the results to the frontend via Tauri events. This architecture enables low-latency visualization of microphone and speaker levels without processing audio in the browser.
Architecture Overview
The audio monitoring system resides in frontend/src-tauri/src/audio/level_monitor.rs and operates as a background service within the Tauri application. It maintains thread-safe state containers for device names and active CPAL streams, ensuring that multiple audio sources can be monitored concurrently without blocking the main thread.
The monitor follows a producer-consumer pattern: CPAL stream callbacks produce raw audio data, a Tokio task consumes calculated levels every 100 milliseconds, and the Tauri event system broadcasts updates to the frontend.
Core Implementation in level_monitor.rs
Initializing the Monitor
Create a new monitor instance using AudioLevelMonitor::new(), which initializes two thread-safe containers: one for tracking device names and another for storing active CPAL streams.
use meetily::audio::level_monitor::AudioLevelMonitor;
let monitor = AudioLevelMonitor::new();
// monitor.devices: Arc<Mutex<Vec<String>>>
// monitor.streams: Arc<Mutex<Vec<Stream>>>
Starting the CPAL Streams
The start_monitoring method opens a CPAL host, resolves each device name using find_device_by_name, and builds dedicated streams via create_level_stream. This function supports both input and output devices, enabling monitoring of microphones and speakers simultaneously.
pub async fn start_monitoring(
&mut self,
app_handle: tauri::AppHandle,
device_names: Vec<String>
) -> Result<(), Box<dyn std::error::Error>> {
// Checks for existing sessions and stops them
// Opens CPAL host and builds streams for each device
}
RMS and Peak Calculation
Inside each CPAL stream callback, the system converts multi-channel audio to mono using audio_to_mono (from audio_processing.rs), then calculates RMS and peak values:
// Excerpt from create_level_stream callback
let rms = (samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32).sqrt();
let peak = samples.iter().fold(0.0_f32, |m, &s| m.max(s.abs()));
let data = AudioLevelData {
device_name: device_name.clone(),
device_type: device_type.to_string(),
rms_level: rms,
peak_level: peak,
is_active: rms > 0.001,
};
level_data.lock().await.push(data);
The RMS value represents the root-mean-square of the audio signal, providing a perceptually accurate measure of loudness normalized between 0.0 and 1.0.
Emitting Updates to the Frontend
A background Tokio task wakes every 100 milliseconds, drains the collected level data from the shared vector, and emits it via Tauri's event system:
// Inside the emission task
let mut interval = tokio::time::interval(Duration::from_millis(100));
loop {
interval.tick().await;
let mut levels = level_data.lock().await;
if !levels.is_empty() {
let update = AudioLevelUpdate {
timestamp: chrono::Utc::now().to_rfc3339(),
levels: levels.drain(..).collect(),
};
app_handle.emit("audio-levels", &update).unwrap();
}
}
Device Discovery and Stream Management
The find_device_by_name function iterates through both input and output devices in the default CPAL host to locate requested devices by name. This dual-mode capability allows the monitor to track system audio outputs as well as microphone inputs.
When stopping the monitor via stop_monitoring, the system clears the is_monitoring flag, drops all CPAL streams (which automatically stops audio capture), and empties the device list, ensuring clean resource cleanup.
Frontend Integration
The frontend listens for the "audio-levels" event using Tauri's event API. Each payload contains an AudioLevelUpdate struct with a timestamp and vector of AudioLevelData objects containing device_name, rms_level, peak_level, and is_active fields.
import { listen } from '@tauri-apps/api/event';
import type { AudioLevelUpdate } from '@/types';
listen<AudioLevelUpdate>('audio-levels', (event) => {
const { timestamp, levels } = event.payload;
levels.forEach((level) => {
const rmsPercentage = (level.rms_level * 100).toFixed(1);
const bar = document.getElementById(`meter-${level.device_name}`);
if (bar) {
bar.style.width = `${rmsPercentage}%`;
}
});
});
Backend Commands
Expose the monitoring functionality to the frontend by registering Tauri commands in frontend/src-tauri/src/lib.rs:
#[tauri::command]
pub async fn start_audio_monitor(
app: tauri::AppHandle,
device_names: Vec<String>,
) -> Result<(), String> {
let mut monitor = meetily::audio::level_monitor::AudioLevelMonitor::new();
monitor
.start_monitoring(app, device_names)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn stop_audio_monitor() -> Result<(), String> {
AUDIO_LEVEL_MONITOR
.stop_monitoring()
.await
.map_err(|e| e.to_string())
}
Store the monitor instance globally (e.g., via lazy_static or Tauri state management) to maintain persistence across command calls.
Summary
- RMS calculation occurs inside CPAL stream callbacks in
frontend/src-tauri/src/audio/level_monitor.rs, computing the square root of the mean squared samples for perceptually accurate level detection. - Update frequency is fixed at 100 milliseconds via a Tokio interval task that batches and emits level data through the
"audio-levels"Tauri event. - Dual device support enables monitoring of both input (microphone) and output (speaker) devices through the same
find_device_by_nameimplementation. - Resource cleanup happens automatically when calling
stop_monitoring, which drops CPAL streams and clears internal state vectors.
Frequently Asked Questions
What is RMS and why does Meetily use it for audio levels?
RMS (Root-Mean-Square) calculates the square root of the average of squared audio samples, providing a mathematical representation of signal energy that correlates closely with human perception of loudness. Meetily uses RMS values normalized between 0.0 and 1.0 to drive visual level meters that accurately reflect perceived volume rather than instantaneous peak values.
How often does Meetily emit audio level updates to the frontend?
The backend emits updates every 100 milliseconds via a Tokio async task that drains the accumulated AudioLevelData vector and broadcasts it through the "audio-levels" Tauri event. This interval balances real-time responsiveness with processing overhead.
Can I monitor both input and output devices simultaneously in Meetily?
Yes. The find_device_by_name function searches both input and output device lists in the CPAL host, and start_monitoring accepts multiple device names in a single vector. You can mix microphone and speaker devices in the same monitoring session, with each source reporting independent RMS and peak levels.
How do I properly stop the audio monitoring to release system resources?
Call the stop_monitoring method on your AudioLevelMonitor instance, which clears the internal is_monitoring flag, drops all active CPAL streams, and empties the device list. In the Tauri command layer, ensure you maintain a global or state-managed instance of the monitor so that stop_monitoring affects the same object that started the streams.
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 →