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.
This commit is contained in:
dailz
2026-06-21 09:10:28 +08:00
parent b4b9990efe
commit 46e7a9785d
+6
View File
@@ -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());