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));
|
let mut interval = tokio::time::interval(Duration::from_millis(20));
|
||||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
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 {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
|
|
||||||
let current_volumes = peer_volumes_mixer.lock().await.clone();
|
let current_volumes = peer_volumes_mixer.lock().await.clone();
|
||||||
let mut active_levels = Vec::new();
|
|
||||||
let mut peer_frames = Vec::new();
|
let mut peer_frames = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut guard = jitter_mixer.lock().await;
|
let mut guard = jitter_mixer.lock().await;
|
||||||
for (&peer_id, buffer) in guard.iter_mut() {
|
for (&peer_id, buffer) in guard.iter_mut() {
|
||||||
// `None` means idle/buffering: contribute nothing
|
// `None` means idle/buffering: contribute nothing,
|
||||||
// and report a zero level so the UI shows idle.
|
// but keep a (zero) entry so the UI sees it idle.
|
||||||
let Some(mut frame) = buffer.pop_frame() else {
|
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;
|
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 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 rms = (sum_sq / frame.len().max(1) as f32).sqrt();
|
||||||
let level = (rms / 32768.0).clamp(0.0, 1.0);
|
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);
|
peer_frames.push(frame);
|
||||||
}
|
}
|
||||||
@@ -379,7 +388,13 @@ async fn run_core_loop(
|
|||||||
break;
|
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