libopenh264 in the vendored ffmpeg build does not rate-control at all. It is not that the
requested bitrate is approximate — it has no effect worth measuring, in either direction.
Found while benchmarking the Linux export (#544, #552). It is not specific to that work: every
Linux export goes through this encoder, so the quality of every Linux export is whatever the
encoder feels like, and the setting we expose does not reach it.
Reproduction
120 frames of 1280×720, deliberately incompressible content (so the encoder cannot come in
under target by accident), encoded against the vendored libavcodec with nothing but the
bitrate knobs changed:
requested bit_rate |
rc_max_rate |
rc_mode |
produced |
| 2 Mbps |
— |
default (quality) |
93.5 Mbps |
| 8 Mbps |
— |
default (quality) |
97.5 Mbps |
| 2 Mbps |
— |
bitrate |
94.0 Mbps |
| 8 Mbps |
— |
bitrate |
97.7 Mbps |
| 2 Mbps |
2 Mbps |
bitrate |
94.0 Mbps |
| 8 Mbps |
8 Mbps |
bitrate |
97.7 Mbps |
Asked for 2 Mbps with an explicit 2 Mbps ceiling, it emits 94 Mbps — 47× over. Quadrupling
the request moves the output by 4%.
Neither rc_mode nor rc_max_rate changes anything.
What this looks like in a real export
On screen-recording content the symptom inverts, which is why it went unnoticed: a mostly
static screen is very compressible, so the encoder undershoots instead.
|
requested |
produced |
| a 60 s 1080p60 export |
8 Mbps |
1.75 Mbps |
| the same, requested 12 |
12 Mbps |
0.72 Mbps |
the same content via h264_vaapi |
8 Mbps |
6.95 Mbps |
So the bitrate we compute from the output resolution — (w * h * 8 Mbps) / (1920 * 1080),
pipeline_linux.rs — never reaches the encoder. Export quality does not scale with resolution,
and raising the setting does not raise quality. On heavy content the same defect produces a
file many times larger than asked for.
h264_vaapi in the same build honours the request, which is what makes this look like a
libopenh264-specific defect rather than a wiring mistake on our side.
What is ruled out
- Not the option plumbing.
av_opt_set(priv_data, "rc_mode", "bitrate") returns 0, and the
value is still 1 when read back after avcodec_open2. The encoder's own option table
lists rc_mode with quality as default and bitrate/buffer/timestamp alongside.
- Not
bit_rate being clobbered at open. It reads back as 8000000 after avcodec_open2.
- Not a ceiling. Setting
rc_max_rate equal to the target changes nothing.
- Not content-dependent. The table above is incompressible input, where a working rate
control would have the most obvious job to do.
Where I would look
- Whether the wrapper forwards the target into OpenH264's
SEncParamExt —
iTargetBitrate and sSpatialLayers[0].iSpatialBitrate — or leaves them at defaults.
- Whether the bundled OpenH264 is built without its rate-control module, in which case the
setting has nowhere to land regardless of what the wrapper does.
I could not fix this from application code, which is why this is an issue rather than a PR: no
combination of the options ffmpeg exposes for this encoder had any effect.
Why it is worth fixing rather than working around
Beyond the quality problem, it makes the software encoder impossible to compare against
anything. Benchmarking it against h264_vaapi required pinning VAAPI to the bitrate
libopenh264 happens to emit, otherwise the comparison reports a quality difference as a speed
difference. Any future encoder work runs into the same wall.
Measured on AMD Ryzen 5 7520U, Ubuntu 24.04, against
crates/thirdparty/ffmpeg-linux64-lgpl-shared. The probe is ~40 lines of C linked against the
vendored libraries; happy to attach it if useful.
libopenh264in the vendored ffmpeg build does not rate-control at all. It is not that therequested bitrate is approximate — it has no effect worth measuring, in either direction.
Found while benchmarking the Linux export (#544, #552). It is not specific to that work: every
Linux export goes through this encoder, so the quality of every Linux export is whatever the
encoder feels like, and the setting we expose does not reach it.
Reproduction
120 frames of 1280×720, deliberately incompressible content (so the encoder cannot come in
under target by accident), encoded against the vendored
libavcodecwith nothing but thebitrate knobs changed:
bit_raterc_max_raterc_modequality)quality)bitratebitratebitratebitrateAsked for 2 Mbps with an explicit 2 Mbps ceiling, it emits 94 Mbps — 47× over. Quadrupling
the request moves the output by 4%.
Neither
rc_modenorrc_max_ratechanges anything.What this looks like in a real export
On screen-recording content the symptom inverts, which is why it went unnoticed: a mostly
static screen is very compressible, so the encoder undershoots instead.
h264_vaapiSo the bitrate we compute from the output resolution —
(w * h * 8 Mbps) / (1920 * 1080),pipeline_linux.rs— never reaches the encoder. Export quality does not scale with resolution,and raising the setting does not raise quality. On heavy content the same defect produces a
file many times larger than asked for.
h264_vaapiin the same build honours the request, which is what makes this look like alibopenh264-specific defect rather than a wiring mistake on our side.
What is ruled out
av_opt_set(priv_data, "rc_mode", "bitrate")returns 0, and thevalue is still
1when read back afteravcodec_open2. The encoder's own option tablelists
rc_modewithqualityas default andbitrate/buffer/timestampalongside.bit_ratebeing clobbered at open. It reads back as 8000000 afteravcodec_open2.rc_max_rateequal to the target changes nothing.control would have the most obvious job to do.
Where I would look
SEncParamExt—iTargetBitrateandsSpatialLayers[0].iSpatialBitrate— or leaves them at defaults.setting has nowhere to land regardless of what the wrapper does.
I could not fix this from application code, which is why this is an issue rather than a PR: no
combination of the options ffmpeg exposes for this encoder had any effect.
Why it is worth fixing rather than working around
Beyond the quality problem, it makes the software encoder impossible to compare against
anything. Benchmarking it against
h264_vaapirequired pinning VAAPI to the bitratelibopenh264 happens to emit, otherwise the comparison reports a quality difference as a speed
difference. Any future encoder work runs into the same wall.
Measured on AMD Ryzen 5 7520U, Ubuntu 24.04, against
crates/thirdparty/ffmpeg-linux64-lgpl-shared. The probe is ~40 lines of C linked against thevendored libraries; happy to attach it if useful.