fix(encoder): ask for CBR — the capped-VBR triad was never a constant rate

The stream was configured as "@8 Mbps" and averaged 5.1. The cause is the argument
set, not the machine and not YouTube: `-b:v K -maxrate K -bufsize 2K` is capped
VBR. Measured on the bundled BtbN build with a static 640x480 frame at -b:v 2000k:

  default rate control   9,858 bytes / 8 s  =     9 kbps
  -rc cbr            2,001,989 bytes / 8 s  = 1,951 kbps
  -rc vbr                9,858 bytes / 8 s  =     9 kbps

On high-motion testsrc2 the same target produced 2006 kbps (CBR) against 1741
(VBR): VBR undershoots by however easy the picture is, and a composited scene
with a still background is very easy.

Spelling is per encoder, verified rather than assumed:
- h264_nvenc, h264_amf: `-rc cbr`.
- h264_qsv: nothing. It has no `-rc`, and libavcodec/qsvenc.c (select_rc_mode,
  FFmpeg d263fce2b2) selects MFX_RATECONTROL_CBR when bitrate == maxrate — which
  the triad already sets. Passing -rc here would be an unknown option.
- libopenh264: nothing. `-rc_mode bitrate` changed no measurable output on this
  build (3,832 vs 3,830 bytes), so it stays out rather than adding a line whose
  effect nobody can demonstrate.

Reference: YouTube's encoder spec requires CBR
(support.google.com/youtube/answer/2853702).

668 tests pass, build 0 warnings.
This commit is contained in:
2026-11-12 07:57:36 -08:00
parent 37ef74eb8d
commit 700c7a8c23
3 changed files with 54 additions and 0 deletions
+27
View File
@@ -316,6 +316,33 @@ public class FfmpegEncoderTests
Assert.Equal("240", args[keyint + 1]);
}
[Fact]
public void Args_AskForConstantBitrateWhereTheEncoderNeedsTelling()
{
var options = new EncoderOptions { RtmpUrl = "rtmp://x/y", BitrateKbps = 12000 };
// Both hardware encoders own a rate-control switch; without it the target is an average to
// undershoot (measured: 9 kbps against a 2000k target on a static frame).
foreach (var encoder in new[] { "h264_nvenc", "h264_amf" })
{
var args = FfmpegArgs.Build(options, encoder).ToArray();
var rc = Array.IndexOf(args, "-rc");
Assert.True(rc >= 0, $"{encoder} must be told the rate-control mode");
Assert.Equal("cbr", args[rc + 1]);
}
// QSV has no -rc: libavcodec/qsvenc.c selects CBR when bitrate == maxrate, so the triad alone
// is the request. Passing -rc there would be an unknown option.
var qsv = FfmpegArgs.Build(options, "h264_qsv").ToArray();
Assert.DoesNotContain("-rc", qsv);
Assert.Equal(qsv[Array.IndexOf(qsv, "-b:v") + 1], qsv[Array.IndexOf(qsv, "-maxrate") + 1]);
// OpenH264 gains nothing measurable from -rc_mode bitrate; it stays out of the command line.
var openh264 = FfmpegArgs.Build(options, "libopenh264").ToArray();
Assert.DoesNotContain("-rc", openh264);
Assert.DoesNotContain("-rc_mode", openh264);
}
[Fact]
public void Args_IncludeInputOutputAndCompliance()
{