feat(dred): continuous DRED tuning, PMTUD, extended Opus6k window
- DredTuner: maps live network metrics (loss/RTT/jitter) to continuous DRED duration every ~500ms instead of discrete tier-locked values. Includes jitter-spike detection for pre-emptive Starlink-style boost. - Opus6k DRED extended from 500ms to 1040ms (max libopus 1.5 supports) - PMTUD: quinn MtuDiscoveryConfig with upper_bound=1452, 300s interval - TrunkedForwarder respects discovered MTU (was hard-coded 1200) - QuinnPathSnapshot exposes quinn internal stats + discovered MTU - AudioEncoder trait: set_expected_loss() + set_dred_duration() methods - PathMonitor: sliding-window jitter variance for spike detection - Integrated into both Android and desktop send tasks in engine.rs - 14 new tests (10 tuner unit + 4 encoder integration) - Updated ARCHITECTURE.md, PROGRESS.md, PRD-dred-integration, PRD-mtu Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -513,6 +513,13 @@ impl CallEngine {
|
||||
encoder.set_aec_enabled(false);
|
||||
let mut buf = vec![0i16; frame_samples];
|
||||
|
||||
// Continuous DRED tuning: poll quinn path stats every 25
|
||||
// frames (~500 ms at 20 ms/frame) and adjust DRED duration +
|
||||
// expected-loss hint based on real-time network conditions.
|
||||
let mut dred_tuner = wzp_proto::DredTuner::new(config.profile.codec);
|
||||
let mut frames_since_dred_poll: u32 = 0;
|
||||
const DRED_POLL_INTERVAL: u32 = 25;
|
||||
|
||||
let mut heartbeat = std::time::Instant::now();
|
||||
let mut last_rms: u32 = 0;
|
||||
let mut last_pkt_bytes: usize = 0;
|
||||
@@ -602,6 +609,34 @@ impl CallEngine {
|
||||
Err(e) => error!("encode: {e}"),
|
||||
}
|
||||
|
||||
// DRED tuner: poll quinn path stats periodically and
|
||||
// adjust encoder DRED duration + expected-loss hint.
|
||||
frames_since_dred_poll += 1;
|
||||
if frames_since_dred_poll >= DRED_POLL_INTERVAL {
|
||||
frames_since_dred_poll = 0;
|
||||
let snap = send_t.quinn_path_stats();
|
||||
let pq = send_t.path_quality();
|
||||
if let Some(tuning) = dred_tuner.update(
|
||||
snap.loss_pct,
|
||||
snap.rtt_ms,
|
||||
pq.jitter_ms,
|
||||
) {
|
||||
encoder.apply_dred_tuning(tuning);
|
||||
if wzp_codec::dred_verbose_logs() {
|
||||
info!(
|
||||
dred_frames = tuning.dred_frames,
|
||||
dred_ms = tuning.dred_frames as u32 * 10,
|
||||
expected_loss = tuning.expected_loss_pct,
|
||||
quinn_loss = format!("{:.1}", snap.loss_pct),
|
||||
quinn_rtt = snap.rtt_ms,
|
||||
jitter = pq.jitter_ms,
|
||||
spike = dred_tuner.spike_boost_active(),
|
||||
"DRED tuner adjusted encoder"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat every 2s with capture+encode+send state
|
||||
if heartbeat.elapsed() >= std::time::Duration::from_secs(2) {
|
||||
let fs = send_fs.load(Ordering::Relaxed);
|
||||
@@ -1250,6 +1285,11 @@ impl CallEngine {
|
||||
encoder.set_aec_enabled(false); // OS AEC or none
|
||||
let mut buf = vec![0i16; frame_samples];
|
||||
|
||||
// Continuous DRED tuning (same as Android send task).
|
||||
let mut dred_tuner = wzp_proto::DredTuner::new(config.profile.codec);
|
||||
let mut frames_since_dred_poll: u32 = 0;
|
||||
const DRED_POLL_INTERVAL: u32 = 25;
|
||||
|
||||
loop {
|
||||
if !send_r.load(Ordering::Relaxed) {
|
||||
break;
|
||||
@@ -1285,6 +1325,21 @@ impl CallEngine {
|
||||
}
|
||||
Err(e) => error!("encode: {e}"),
|
||||
}
|
||||
|
||||
// DRED tuner: poll quinn path stats periodically.
|
||||
frames_since_dred_poll += 1;
|
||||
if frames_since_dred_poll >= DRED_POLL_INTERVAL {
|
||||
frames_since_dred_poll = 0;
|
||||
let snap = send_t.quinn_path_stats();
|
||||
let pq = send_t.path_quality();
|
||||
if let Some(tuning) = dred_tuner.update(
|
||||
snap.loss_pct,
|
||||
snap.rtt_ms,
|
||||
pq.jitter_ms,
|
||||
) {
|
||||
encoder.apply_dred_tuning(tuning);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user