Repository navigation
v0.3.0 - Real-world quality: rate control fixed
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_percentageapplied on the sequence-parameter bitrate path, order-independently. Affected ffmpeg's-b:vCLI default (2× RC target); not exercised by Sunshine, which sendstarget_percentage=100.BC250_DEBUG_RC=1diagnostic added, andBC250_PERF_FRAMEnow logsqp/meas_fps/target_bpfbesidebytes. 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=12floor rather than bandwidth (~19 of 31 Mbps used at 1440p) — the next quality lever. rc_estimate_base_qp()saturates atqp_minabove 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.