Skip to content

fix(stream): let congestion control back off below the bitrate target - #116

Draft
szdziedzic wants to merge 1 commit into
szdziedzic-claude/capture-poll-240hzfrom
szdziedzic-claude/webrtc-bwe-floor
Draft

szdziedzic wants to merge 1 commit into
szdziedzic-claude/capture-poll-240hzfrom
szdziedzic-claude/webrtc-bwe-floor

Conversation

@szdziedzic

@szdziedzic szdziedzic commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

WebRTCBitratePolicy sets the bandwidth estimator's minimum to 10% of the target with a 250 kbps absolute floor, clamped to the target for tiny configs. The maximum and the starting estimate stay at the target.

Why

The minimum was pinned at 90% of target. On a path that could not carry that rate the sender kept overshooting: standing queues, loss, recovery keyframes and freezes instead of a quality dip. The pin also made raising --video-bitrate risky, because a higher target forced a higher floor onto constrained links.

How

  • One change in WebRTCBitratePolicy: minimumBitsPerSecond = min(target, max(250_000, target / 10)).
  • Ramp-up on healthy paths is unchanged because applyBitrateSettings already passes the maximum as the current estimate. Measured on loopback (3 Mbps target, 60 fps, idle → 8 s scroll → idle): the estimate held flat at 2813 kbps through all three phases; it only gains room to shrink when the path is genuinely constrained.
  • Verified: policy tests for the 10% floor, the 250 kbps absolute floor, and the clamp for a 100 kbps target.

Part of the tap-to-photon stack (bottom → top): latency probe → send frames on arrival → 240 Hz seed poll → bitrate floor → playout window → data-channel input → lossy moves lane. Everything is on by default, so a package built from any layer works with the unchanged EAS launch flags (--transport webrtc --webrtc-codec vp8 --max-dimension 960 --video-bitrate 6000000 --video-fps 60).

Measured with scripts/latency-probe (M5 Pro, loopback, iPhone 17 / iOS 26.5, 25 taps): 0.1.51 p50 32–37 ms / p95 ≈ 43 ms → top of stack p50 27 ms / p95 38 ms, no outliers above 40 ms.

🤖 Generated with Claude Code

WebRTCBitratePolicy pinned the estimator's minimum at 90% of target, so
on a path that could not carry ~2.7 Mbps the sender kept overshooting:
standing queues, loss, recovery keyframes, and freezes instead of a
quality dip. It also blocks raising the ceiling — with a 90% floor, a
higher target forces a higher floor onto constrained links.

The minimum is now 10% of target with a 250 kbps absolute floor,
clamped to the target for tiny configs. The starting estimate stays at
the target (applyBitrateSettings already passes the maximum as the
current estimate), so ramp-up on healthy paths is unchanged.

Measured on loopback (3 Mbps target, 60 fps, idle -> 8 s scroll burst
-> idle): old policy held 2810 kbps and decayed onto its 2700 floor;
new policy held flat at 2813 kbps through all three phases with fps at
59-60 — the estimate does not sag toward the lower floor on a clean
path, it only gains room to shrink when the path is genuinely
constrained. With the floor fixed, deployments can raise
--video-bitrate (e.g. 3M -> 6M) to double the bits available to scrolls
and transitions without risking overshoot freezes on bad networks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant