diff --git a/src/codec/ffmpeg_encoder.cpp b/src/codec/ffmpeg_encoder.cpp index 84cb4fb..4f57727 100644 --- a/src/codec/ffmpeg_encoder.cpp +++ b/src/codec/ffmpeg_encoder.cpp @@ -417,7 +417,15 @@ CodecResult> 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(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(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