Skip to content

v0.3.0 - Real-world quality: rate control fixed

Choose a tag to compare

@Shalasere Shalasere released this 10 Sep 18:57
· 228 commits to main since this release

Real-world quality release. The long-standing "corruption during on-screen motion" report — the one that made this driver unusable for actual Sunshine/Moonlight streaming — is fixed, and it was not where previous releases said it was.

The fix

CBR filler bytes were being fed back into the rate-control feedback loop.

maybe_append_filler() pads each frame with a spec-legal filler_data_rbsp() NAL up to the per-frame bit target, and both encode paths handed that padded size to rc_update_stats(). Because filler exists precisely to make the frame hit the target, reported bits always cancelled the bucket drain, so buffer_fullness could never fall. The large opening IDR pushed it to its clamp, error stayed positive permanently, the integral term wound to full range, and QP saturated at its 51 maximum for entire sessions — every frame coded at the worst quantization the encoder allows, then padded back up so the bitrate looked correct.

That is why raising the requested bitrate never helped: the extra bandwidth bought padding, not picture.

Measured on a real remote client (2560×1440, 31 Mbps requested, 74 s, 2490 frames):

before after
QP pinned 51 avg 12.0 (at the floor, near-lossless)
frame bytes constant 64,559 (padding) 2,084 – 148,542 (real picture)
delivered 28.2 Mbps, mostly filler 18.7 Mbps of real content

Also fixed

  • Rate-control bucket now drains by wall-clock time rather than a fixed per-frame quota. The fixed quota silently enforced the negotiated frame rate: a 1440p session negotiates 60 fps while this encoder sustains ~40, making the budget a third too small — measured 20.91 Mbps against a 30.99 Mbps request, with bits/frame matching the target to 0.01%.
  • target_percentage applied on the sequence-parameter bitrate path, order-independently. Affected ffmpeg's -b:v CLI default (2× RC target); not exercised by Sunshine, which sends target_percentage=100.
  • BC250_DEBUG_RC=1 diagnostic added, and BC250_PERF_FRAME now logs qp / meas_fps / target_bpf beside bytes. This is what exposed the bug.

Corrected guidance

Previous releases attributed the motion corruption upstream of this driver, to Sunshine's KMS capture or the compositor, on the evidence that pre-encode input and post-encode reconstruction were pixel-identical during a corrupted moment. That evidence came from a loopback test harness whose Moonlight client rendered onto the very display Sunshine was capturing — a video feedback loop compounding 4:2:0 loss every round trip. The conclusion was wrong. docs/DEVLOG.md §14–§16 records the five hypotheses investigated and discarded (chroma QP, the 10-bit capture path, a GPU-sync gap, luma-only deblocking, bitrate plumbing), four of which were argued from how the corruption looked, and all of which were wrong.

Newly documented prerequisite

Disable screen blanking on the host. Sunshine re-initializes KMS capture on every app launch; if the display has slept it reads the output as 0x0 and returns "Failed to initialize video capture/encoding" (Moonlight Error 503) to the client — even though Sunshine itself started cleanly hours earlier. This is the most likely reason a correct install appears broken. Verify with cat /sys/class/drm/card*-DP-1/enabled (kscreen-doctor -o is not reliable for this).

Known limitations

  • Quality is now bounded by the qp_min=12 floor rather than bandwidth (~19 of 31 Mbps used at 1440p) — the next quality lever.
  • rc_estimate_base_qp() saturates at qp_min above roughly 31 Mbps at 1440p30.
  • In-loop deblocking is luma-only while the bitstream signals disable_deblocking_filter_idc=0; I-slice intra prediction reads source rather than reconstructed neighbours. Together ~3.7 dB of per-GOP drift; not visually significant.
  • H.265/HEVC remains a non-functional stub — do not enable it.