feat(stats): populate frame_age metric for WebRTC path (partial #20)
Add quantitative capture-to-send latency measurement so we can diagnose remaining latency sources after #24/#25 PTS fixes. Previously frame_age_p95 was always 0.0ms because the WebRTC code path never propagated capture timestamps, even though stats.rs already supported the metric. The infrastructure existed but was disconnected. Changes: - avhw.rs: Add capture_time: Instant field to CpuNv12Frame (set when PipeWire delivers frame) and EncodedH264Frame (propagated through encode thread via new last_capture_time side-channel on SwEncEncode). - state_portal.rs: Change sent_gap channel type from Sender<f64> to Sender<(f64, Option<f64>)> so WebRTC thread can send pre-computed age_ms = capture_time.elapsed() at the exact send moment (not at stats drain time, which would inflate the measurement by ~1s). - stats.rs: record_send_from_thread now accepts Option<f64> age_ms and pushes to frame_age_ms Vec when Some. After this commit: - stats: log lines show real frame_age_p95 / frame_age_max in ms - Expected range: 5-30ms (import + scale + encode + channel send) - If much higher: server pipeline has queueing issue - If low but user still sees latency: confirms bottleneck is network or browser-side (jitter buffer, decode queue) Scope notes: - Only Portal/PipeWire path is instrumented. wlr-screencopy path uses different code path (EncState, not SwEncState) and will continue to report frame_age=0.0ms. Adding wlr instrumentation is separate scope. - This is diagnostic only — does NOT change user-visible behavior. No encoding, sending, or stats output format changes. Tests: - cargo build --release: 0 new warnings (19 baseline preserved) - cargo test: 96 lib + 3 integration, 0 failed - SAFETY comments preserved verbatim - 3 files changed, +39/-10 lines Refs #20.
This commit is contained in:
+7
-3
@@ -168,13 +168,17 @@ impl PipelineStats {
|
||||
|
||||
/// Record a frame sent from a background WebRTC thread.
|
||||
/// `gap_ms` is the pre-computed time since the previous send (0.0 = first frame).
|
||||
/// Unlike `record_send`, this does not sample `Instant::now()`, so it remains
|
||||
/// accurate even when batch-drained at stats snapshot time.
|
||||
pub fn record_send_from_thread(&mut self, gap_ms: f64) {
|
||||
/// `age_ms` is the pre-computed capture-to-send latency (None if unavailable).
|
||||
/// Both are pre-computed on the sending thread to remain accurate when
|
||||
/// batch-drained at stats snapshot time on the main thread.
|
||||
pub fn record_send_from_thread(&mut self, gap_ms: f64, age_ms: Option<f64>) {
|
||||
if gap_ms > 0.0 {
|
||||
self.sent_gaps_ms.push(gap_ms);
|
||||
}
|
||||
self.sent_frames += 1;
|
||||
if let Some(age) = age_ms {
|
||||
self.frame_age_ms.push(age);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update PipeWire dropped counter (absolute value from AtomicU64).
|
||||
|
||||
Reference in New Issue
Block a user