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
+7
View File
@@ -91,6 +91,13 @@ mixer writes IEEE-float stereo to a Windows named pipe** (`-f f32le -ar 48000 -a
anullsrc` silence in the TASK 8 audio milestone) + explicit `-map 0:v -map 1:a` + `-c:v <enc> -b:v K
-maxrate K -bufsize 2K` + **`-g fps×4 -keyint_min fps×4 -sc_threshold 0 -bf 0 -pix_fmt yuv420p`**
(≤4s keyframes, closed GOP, H.264 compliance) + `-c:a aac -ar 48000 -ac 2 -f flv <rtmpUrl>`.
**Rate control is CBR and it is per-encoder** (`FfmpegArgs.AddRateControl`). The `-b:v/-maxrate/-bufsize`
triad is **capped VBR, not CBR**: measured 2026-11-12 on the bundled BtbN build, a static frame at
`-b:v 2000k` produced **9 kbps** under NVENC's default and **1951 kbps** with `-rc cbr` — the cause of an
@8 stream that averaged 5.1 Mbps. NVENC and AMF are passed `-rc cbr`; **QSV has no `-rc`** and selects CBR
itself when `bitrate == maxrate` (`libavcodec/qsvenc.c`, `select_rc_mode`), so the triad is its whole ask;
OpenH264's `-rc_mode bitrate` changed nothing measurable here and is not passed. YouTube's encoder spec
requires CBR. Never assume `-maxrate` equals CBR — it does not.
**Record output (TASK 18):** `FfmpegArgs.Build` emits one self-contained block per output, each its own
`-map 0:v -map 1:a` + codec tags (`AddVideoTags` helper). Stream block = `-f flv <rtmpUrl>`; record block =
`-f mp4 <RecordPath>`. `EncoderOptions.StreamEnabled`/`RecordEnabled`/`RecordPath` gate each block, so