Skip to content

fix(videoenc): cap VBR excursions and bound frame sizes via VBV - #631

Merged
srperens merged 1 commit into
mainfrom
fix/videoenc-vbv-cap
Jun 5, 2026
Merged

fix(videoenc): cap VBR excursions and bound frame sizes via VBV#631
srperens merged 1 commit into
mainfrom
fix/videoenc-vbv-cap

Conversation

@srperens

@srperens srperens commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

The video encoder block sets only target bitrate, leaving peak behavior unconstrained. This PR bounds how far the encoder can spike above target:

  • NVENC (nv*): max-bitrate = 1.2× target (VBR ceiling, ignored in CBR mode), vbv-buffer-size = 0.5 s worth of target bitrate. Previously both were left at defaults (max-bitrate unset, vbv-buffer-size=0 = "NVENC default"), allowing single frames ~10× the average frame size.
  • x264/x265: vbv-buf-capacity = 500 ms, set explicitly instead of relying on the upstream 600 ms default.

Both are guarded with has_property so encoder variants lacking the knobs are unaffected.

Background

Diagnosed on a live WHEP viewer session (sender-side packet capture + client media stats): a stream averaging ~27 Mbit/s emitted 200–450 KB frames, each leaving the NIC as 160–360 RTP packets back-to-back at line rate (~14 µs spacing, 65–79 Mbit/s per 100 ms window). Those bursts overflow shallow buffers on constrained viewer paths and the tail gets dropped — observed as bursty packet loss (+200–400 packets at a time) while average bitrate, jitter (13–19 ms) and RTT (~26 ms) all looked healthy.

Companion to #629 (RTX repairs residual loss; this PR shrinks the bursts that cause it). The remaining lever is per-flow configuration: lower target bitrate and CBR rate control for outputs facing constrained networks.

🤖 Generated with Claude Code

The encoder block set only target bitrate, leaving peak behavior
unconstrained. On NVENC in VBR mode (the block default), max-bitrate is
unset and vbv-buffer-size=0, so single frames can spike to ~10x the
average frame size. Such frames leave the NIC as line-rate packet
bursts (hundreds of packets back-to-back) that overflow shallow buffers
on constrained viewer paths — observed as bursty packet loss on WebRTC
outputs while average bitrate looked fine.

- NVENC: max-bitrate = 1.2x target (VBR ceiling, ignored in CBR),
  vbv-buffer-size = 0.5 s of target bitrate
- x264/x265: vbv-buf-capacity = 500 ms, set explicitly instead of
  relying on the upstream 600 ms default

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@srperens
srperens merged commit 5b8c5d3 into main Jun 5, 2026
7 checks passed
@srperens
srperens deleted the fix/videoenc-vbv-cap branch June 5, 2026 15:51
srperens added a commit that referenced this pull request Jun 8, 2026
* fix(videoenc): cap VideoToolbox bursts via data-rate-limits

The vtenc branch set only target bitrate, so in ABR mode (the block
default maps VBR/CQP to abr) peak behavior was unconstrained:
data-rate-limits defaults to disabled and single frames can spike far
above the target. Same failure mode as the NVENC VBR fix (#631) -
line-rate packet bursts overflowing shallow buffers on constrained
viewer paths.

Set data-rate-limits to 1.2x target averaged over a 0.5 s window
(kVTCompressionPropertyKey_DataRateLimits) - the VideoToolbox
equivalent of NVENC's max-bitrate + vbv-buffer-size pair. vtenc itself
skips the property in CBR mode, matching NVENC's "ignored in CBR"
semantics.

Verified against VideoToolbox on macOS (GST_DEBUG=vtenc:6): ABR applies
the limit (DataRateLimits 300000 bytes / 0.5 s at 4000 kbps target,
status noErr), CBR logs "Ignoring data-rate-limits property".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(videoenc): floor force-keyunit interval to break PLI/IDR storms

Each WebRTC viewer PLI becomes a force-keyunit event at the encoder,
and min-force-key-unit-interval defaults to 0 (honor every request).
Under burst-induced loss this spirals: a big frame causes packet loss,
the viewer PLIs, the encoder emits an outsized IDR, which causes more
loss and more PLIs - observed as output bursting to ~10x the target
bitrate during high-motion transitions, far above what the encoder's
own rate limits allow for normal frames.

Set a 1 s floor (GstVideoEncoder base-class property, applies to all
encoder implementations). New viewers and genuine recoveries still get
a keyframe quickly; a PLI storm can add at most one extra IDR per
second.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(videoenc): map VBR to CBR on VideoToolbox - ABR ignores bitrate

Measured on Apple Silicon (GStreamer 1.28, 720p30, 5 Mbit/s target,
demanding content): vtenc in ABR mode encodes at ~95 Mbit/s - it does
not rate-control at all. Root cause: vtenc unconditionally sets
kVTCompressionPropertyKey_Quality (property default 0.5) after
AverageBitRate, and Apple Silicon hardware then runs constant-quality
and ignores the bitrate target. A direct VTCompressionSession with the
same settings minus Quality tracks the target correctly (~6.4 Mbit/s),
and adding Quality=0.5 reproduces the runaway (~98 Mbit/s) - upstream
GStreamer bug, to be reported.

CBR uses a different VT key (ConstantBitRate) that is honored in
steady state, so map VBR to CBR: a user asking for VBR means "roughly
the target bitrate", never "unbounded constant quality". CQP keeps
mapping to ABR, whose constant-quality behavior is exactly what CQP
requests.

Also correct the data-rate-limits comment: on Apple Silicon hardware
the cap is accepted (noErr) but has no measurable effect even in ABR,
and scene changes overshoot by ~2-4 MB across ~10 frames in every
mode (intrinsic to Apple's rate control, also bypasses DataRateLimits
in a direct VT session). Strictly bounded bursts require a software
encoder (x264 VBV) via encoder_preference=software.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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