From 46e7a9785dc99d3ff9b52ec701af63eddaf97cc3 Mon Sep 17 00:00:00 2001 From: dailz Date: Sun, 21 Jun 2026 09:10:28 +0800 Subject: [PATCH] fix(webrtc): bypass str0m LeakyBucketPacer for low-latency LAN streaming Final piece of the latency puzzle. Server-side frame_age was 6ms but browser jitterBufferDelay still spiked to 500+ ms during active periods. Root cause found via librarian investigation of str0m 0.20 source: str0m LeakyBucketPacer (active when BWE enabled) limits send rate to BWE_estimate * 1.1. For a 100KB IDR frame at 8Mbps pacing, the pacer queue adds ~100ms of send-side latency. Multiple frames stack during burst encode, causing receiver jitter buffer to grow. Video streams are PACED BY DEFAULT in str0m (audio is unpaced). Evidence: str0m/src/streams/send.rs:1116 default unpaced logic. Fix: call stream_tx.set_unpaced(true) on the video stream when media is added. BWE remains enabled for bitrate adaptation / TWCC feedback, but the pacer no longer throttles our video egress. Why this is the right call for wl-webrtc: - LAN scenario: dedicated link, no competing flows to smooth against - Encoder cap (8 Mbps) + VBV buffer (250ms) already provide rate control - The pacer's smoothing benefit (avoid bursts that compete with TCP) is irrelevant for our use case - BWE adaptation still works (we still receive EgressBitrateEstimate events and adapt encoder bitrate via BitrateCommand::UpdateBitrate) Verification expectations: - Active-period jitterBufferDelay: 500+ ms -> < 200 ms - 'Stop mouse, client continues 10s' symptom: should disappear entirely - Server-side frame_age: unchanged (~6ms) - Bitrate/resolution adaptation: unchanged (still uses BWE) - MP4 mode: unaffected (different code path) Tests: - cargo build --release: 0 new warnings (19 baseline preserved) - cargo test: 96 lib + 3 integration, 0 failed - SAFETY comments preserved verbatim - 1 file changed, 7 insertions(+), 1 deletion(-) Closes the latency investigation started in #23 / #24 / #25. --- src/webrtc.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/webrtc.rs b/src/webrtc.rs index 893f015..bcf5e93 100644 --- a/src/webrtc.rs +++ b/src/webrtc.rs @@ -500,6 +500,12 @@ impl WebRtcInner { } }; self.video_pt = None; + // Disable str0m's LeakyBucketPacer for this video stream. Default pacing + // adds ~100ms send latency per large IDR; our 8Mbps cap + VBV already + // provide rate control. BWE stays enabled for adaptation feedback. + if let Some(stream_tx) = self.rtc.direct_api().stream_tx_by_mid(mid, None) { + stream_tx.set_unpaced(true); + } if let Some(writer) = self.rtc.writer(mid) { for pp in writer.payload_params() { tracing::debug!("Codec: pt={:?} spec={:?}", pp.pt(), pp.spec());