fix(codec): set CRF via x264's private option, not global_quality

The CRF value was set through AVCodecContext.global_quality with
AV_CODEC_FLAG_QSCALE, which FFmpeg's libx264 wrapper divides by
FF_QP2LAMBDA (118) before passing to x264 — turning CRF 16 into
CRF 0.135 (essentially lossless) while x264 logged '-qscale is
ignored, -crf is recommended' and fell back to its own defaults.
The CRF was never actually applied.

Now the CRF is set as x264's private "crf" option via av_opt_set,
which passes the exact value directly to the encoder. The VBV max
rate still caps bursts as before.
This commit is contained in:
2026-09-09 13:28:09 +02:00
parent e82e7853d1
commit 8f1c2aa868
+9 -1
View File
@@ -417,7 +417,15 @@ CodecResult<std::unique_ptr<Encoder>> EncoderFactory::create(const EncoderConfig
// a fixed bitrate, and let the encoder use fewer bits on static screen
// content and more on motion or text. The VBV max rate still caps the
// peak so bursts cannot overflow the receiver's UDP buffers.
ctx->global_quality = static_cast<int>(config.crf);
// CRF rate control: set x264's CRF directly as a private option.
// FFmpeg's global_quality + AV_CODEC_FLAG_QSCALE path divides by
// FF_QP2LAMBDA, giving a wrong CRF value (16/118 ≈ 0.1, essentially
// lossless) — hence the "-qscale is ignored" warning. Setting the
// private "crf" option bypasses that and gives x264 the exact value.
const std::string crf_value = std::to_string(config.crf);
if (av_opt_set(ctx->priv_data, "crf", crf_value.c_str(), 0) < 0) {
return CodecError{"failed to set CRF quality"};
}
ctx->rc_max_rate = static_cast<int64_t>(config.bitrate_kbps) * 1000;
// VBV: one frame period of budget keeps bursts tight — a two-frame
// buffer lets a keyframe spike beyond what a constrained link can