perf: coalesce per-peer audio levels to ~10/sec for the UI
The mixer ran at the fixed 20ms playback cadence and pushed an AudioLevels event every tick — ~50/sec — each waking the iced runtime for a full re-render. Levels are now peak-held per peer across a 100ms window and emitted once per window (~10/sec), cutting UI-bound events 5x. Peak-hold (rather than last-sample) means a brief speech transient inside a window still registers, so the speaking indicator stays responsive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-6
@@ -325,20 +325,28 @@ async fn run_core_loop(
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(20));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
|
||||
// The 20ms mix cadence is fixed by playback, but pushing a
|
||||
// level event every tick floods the UI runtime at ~50/sec. We
|
||||
// peak-hold per-peer levels across this many ticks and emit
|
||||
// once per window (~10/sec) — peak-hold so a brief transient
|
||||
// inside the window still lights the speaking indicator.
|
||||
const LEVEL_EMIT_TICKS: u32 = 5;
|
||||
let mut level_peaks: HashMap<EndpointId, f32> = HashMap::new();
|
||||
let mut ticks_since_emit: u32 = 0;
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let current_volumes = peer_volumes_mixer.lock().await.clone();
|
||||
let mut active_levels = Vec::new();
|
||||
let mut peer_frames = Vec::new();
|
||||
|
||||
{
|
||||
let mut guard = jitter_mixer.lock().await;
|
||||
for (&peer_id, buffer) in guard.iter_mut() {
|
||||
// `None` means idle/buffering: contribute nothing
|
||||
// and report a zero level so the UI shows idle.
|
||||
// `None` means idle/buffering: contribute nothing,
|
||||
// but keep a (zero) entry so the UI sees it idle.
|
||||
let Some(mut frame) = buffer.pop_frame() else {
|
||||
active_levels.push((peer_id, 0.0));
|
||||
level_peaks.entry(peer_id).or_insert(0.0);
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -352,7 +360,8 @@ async fn run_core_loop(
|
||||
let sum_sq: f32 = frame.iter().map(|&x| (x as f32).powi(2)).sum();
|
||||
let rms = (sum_sq / frame.len().max(1) as f32).sqrt();
|
||||
let level = (rms / 32768.0).clamp(0.0, 1.0);
|
||||
active_levels.push((peer_id, level));
|
||||
let peak = level_peaks.entry(peer_id).or_insert(0.0);
|
||||
*peak = peak.max(level);
|
||||
|
||||
peer_frames.push(frame);
|
||||
}
|
||||
@@ -379,7 +388,13 @@ async fn run_core_loop(
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = ui_tx_mixer.send(UiEvent::AudioLevels(active_levels)).await;
|
||||
// Emit coalesced peaks once per window, then reset.
|
||||
ticks_since_emit += 1;
|
||||
if ticks_since_emit >= LEVEL_EMIT_TICKS {
|
||||
let levels: Vec<(EndpointId, f32)> = level_peaks.drain().collect();
|
||||
let _ = ui_tx_mixer.send(UiEvent::AudioLevels(levels)).await;
|
||||
ticks_since_emit = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user