1use alpha2_media::{BitrateMode, VideoRateControl};
8use rsmpeg::avutil::AVDictionary;
9use std::ffi::CStr;
10
11pub(crate) fn constant_mode_option(encoder_name: &str) -> Option<(&'static CStr, &'static CStr)> {
16 if encoder_name.ends_with("_vulkan") {
17 Some((c"rc_mode", c"cbr"))
18 } else if encoder_name.ends_with("_nvenc") || encoder_name.ends_with("_amf") {
19 Some((c"rc", c"cbr"))
20 } else if encoder_name.ends_with("_vaapi") || encoder_name.ends_with("_d3d12va") {
21 Some((c"rc_mode", c"CBR"))
22 } else if encoder_name.ends_with("_mf") {
23 Some((c"rate_control", c"cbr"))
24 } else if encoder_name.ends_with("_videotoolbox") {
25 Some((c"constant_bit_rate", c"1"))
26 } else {
27 None
28 }
29}
30
31pub(crate) fn constant_rate_hint(encoder_name: &str, rate: VideoRateControl) -> &'static str {
35 if rate.mode != BitrateMode::Constant {
36 ""
37 } else if encoder_name.ends_with("_videotoolbox") {
38 " (a constant bit rate was asked for, which VideoToolbox has only on macOS 13 and later)"
39 } else if encoder_name.ends_with("_d3d12va") {
40 " (a constant bit rate was asked for, which D3D12 Video Encode has only on some devices; \
41 ask for a quantizer instead)"
42 } else {
43 ""
44 }
45}
46
47pub(crate) fn rate_control_options(
53 encoder_name: &str,
54 rate: VideoRateControl,
55 options: AVDictionary,
56) -> AVDictionary {
57 match rate.mode {
58 BitrateMode::Variable => options,
59 BitrateMode::Constant => {
60 match mechanism_request(
61 encoder_name,
62 RateControlMechanism::ConstantBitrate,
63 rate.bitrate,
64 0,
65 ) {
66 Some(request) => request.dictionary(options),
67 None => options,
68 }
69 }
70 }
71}
72
73#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
75#[non_exhaustive]
76pub enum RateControlMechanism {
77 ConstantBitrate,
79 VariableBitrate,
81 ConstantQuantizer,
83}
84
85impl RateControlMechanism {
86 pub const ALL: [Self; 3] = [
88 Self::ConstantBitrate,
89 Self::VariableBitrate,
90 Self::ConstantQuantizer,
91 ];
92
93 #[must_use]
95 pub const fn as_str(self) -> &'static str {
96 match self {
97 Self::ConstantBitrate => "cbr",
98 Self::VariableBitrate => "vbr",
99 Self::ConstantQuantizer => "cqp",
100 }
101 }
102}
103
104#[derive(Clone, Debug, Eq, PartialEq)]
106pub(crate) struct MechanismRequest {
107 pub options: Vec<(&'static CStr, String)>,
109 pub bit_rate: i64,
112}
113
114impl MechanismRequest {
115 pub fn describe(&self) -> String {
117 let mut parts = self
118 .options
119 .iter()
120 .map(|(key, value)| format!("{}={value}", key.to_string_lossy()))
121 .collect::<Vec<_>>();
122 if self.bit_rate > 0 {
123 parts.insert(0, format!("b={}", self.bit_rate));
124 }
125 parts.join(" ")
126 }
127
128 pub fn dictionary(&self, mut options: AVDictionary) -> AVDictionary {
130 for (key, value) in &self.options {
131 options = crate::convert::set_encoder_option(options, key, value);
132 }
133 options
134 }
135}
136
137pub(crate) fn mechanism_request(
142 encoder_name: &str,
143 mechanism: RateControlMechanism,
144 bitrate: u64,
145 quantizer: u32,
146) -> Option<MechanismRequest> {
147 let target = i64::try_from(bitrate).unwrap_or(i64::MAX);
148 let constant = |switch: Option<(&'static CStr, &'static str)>| {
149 let mut options = vec![
150 (c"maxrate", bitrate.to_string()),
151 (c"minrate", bitrate.to_string()),
152 (c"bufsize", bitrate.to_string()),
153 ];
154 if let Some((key, value)) = switch {
155 options.push((key, value.to_owned()));
156 }
157 MechanismRequest {
158 options,
159 bit_rate: target,
160 }
161 };
162 let switched = |key: &'static CStr, value: &str| MechanismRequest {
163 options: vec![(key, value.to_owned())],
164 bit_rate: target,
165 };
166 let fixed_quantizer = |options: Vec<(&'static CStr, String)>| MechanismRequest {
167 options,
168 bit_rate: 0,
169 };
170 let q = quantizer.to_string();
171 let family = if encoder_name.ends_with("_vulkan") {
172 "vulkan"
173 } else if encoder_name.ends_with("_nvenc") {
174 "nvenc"
175 } else if encoder_name.ends_with("_vaapi") {
176 "vaapi"
177 } else if encoder_name.ends_with("_d3d12va") {
178 "d3d12va"
179 } else if encoder_name.ends_with("_amf") {
180 "amf"
181 } else if encoder_name.ends_with("_mf") {
182 "mf"
183 } else if encoder_name.ends_with("_videotoolbox") {
184 "videotoolbox"
185 } else if encoder_name.ends_with("_qsv") {
186 "qsv"
187 } else if matches!(encoder_name, "libx264" | "libx265") {
188 "x26x"
189 } else if matches!(
190 encoder_name,
191 "libvpx" | "libvpx-vp9" | "libaom-av1" | "libsvtav1"
192 ) {
193 "crf"
194 } else {
195 "libavcodec"
196 };
197 Some(match (family, mechanism) {
198 ("crf", RateControlMechanism::ConstantBitrate) if encoder_name == "libsvtav1" => {
202 MechanismRequest {
203 options: vec![
204 (c"bufsize", bitrate.to_string()),
205 (c"svtav1-params", "rc=2:pred-struct=1".to_owned()),
206 ],
207 bit_rate: target,
208 }
209 }
210 (_, RateControlMechanism::ConstantBitrate) => constant(
211 constant_mode_option(encoder_name)
212 .map(|(key, value)| (key, value.to_str().unwrap_or_default())),
213 ),
214 ("vulkan", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "vbr"),
215 ("nvenc", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr"),
216 ("vaapi" | "d3d12va", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "VBR"),
217 ("amf", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr_peak"),
218 ("mf", RateControlMechanism::VariableBitrate) => switched(c"rate_control", "u_vbr"),
219 (_, RateControlMechanism::VariableBitrate) => MechanismRequest {
220 options: Vec::new(),
221 bit_rate: target,
222 },
223 ("vulkan", RateControlMechanism::ConstantQuantizer) => {
224 fixed_quantizer(vec![(c"rc_mode", "cqp".to_owned()), (c"qp", q)])
225 }
226 ("nvenc", RateControlMechanism::ConstantQuantizer) => {
227 fixed_quantizer(vec![(c"rc", "constqp".to_owned()), (c"qp", q)])
228 }
229 ("vaapi" | "d3d12va", RateControlMechanism::ConstantQuantizer) => {
232 fixed_quantizer(vec![(c"rc_mode", "CQP".to_owned()), (c"qp", q)])
233 }
234 ("amf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![
235 (c"rc", "cqp".to_owned()),
236 (c"qp_i", q.clone()),
237 (c"qp_p", q),
238 ]),
239 ("x26x", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"qp", q)]),
241 ("crf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"crf", q)]),
242 ("videotoolbox", RateControlMechanism::ConstantQuantizer)
248 if encoder_name == "h264_videotoolbox" =>
249 {
250 fixed_quantizer(videotoolbox_quality_options(quantizer))
251 }
252 _ => return None,
256 })
257}
258
259const FF_QP2LAMBDA: u32 = 118;
261
262const VIDEOTOOLBOX_QUALITY: [(u32, f64); 5] =
269 [(23, 0.65), (27, 0.57), (31, 0.50), (35, 0.40), (41, 0.25)];
270
271pub(crate) fn videotoolbox_quality(quantizer: u32) -> f64 {
274 let table = &VIDEOTOOLBOX_QUALITY;
275 let segment = table
276 .windows(2)
277 .find(|pair| quantizer <= pair[1].0)
278 .unwrap_or(&table[table.len() - 2..]);
279 let ((q0, v0), (q1, v1)) = (segment[0], segment[1]);
280 let position = (f64::from(quantizer) - f64::from(q0)) / (f64::from(q1) - f64::from(q0));
281 let quality = v0 + (v1 - v0) * position;
282 (quality * 100.0).round().clamp(1.0, 100.0) / 100.0
283}
284
285fn videotoolbox_quality_options(quantizer: u32) -> Vec<(&'static CStr, String)> {
288 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
289 let hundredths = (videotoolbox_quality(quantizer) * 100.0).round() as u32;
290 vec![
291 (c"flags", "+qscale".to_owned()),
292 (c"global_quality", (hundredths * FF_QP2LAMBDA).to_string()),
293 ]
294}
295
296#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
298#[non_exhaustive]
299pub enum AppliedRateControl {
300 Quantizer,
302 Quality,
304 Bitrate,
306}
307
308impl AppliedRateControl {
309 #[must_use]
311 pub const fn as_str(self) -> &'static str {
312 match self {
313 Self::Quantizer => "quantizer",
314 Self::Quality => "quality",
315 Self::Bitrate => "bitrate",
316 }
317 }
318
319 pub(crate) fn of_quantizer_request(encoder_name: &str) -> Self {
321 if encoder_name.ends_with("_videotoolbox") {
322 Self::Quality
323 } else {
324 Self::Quantizer
325 }
326 }
327}
328
329pub(crate) fn openh264_rate(
334 rate: VideoRateControl,
335) -> (crate::openh264::OpenH264Rate, AppliedRateControl) {
336 match rate.quantizer {
337 Some(quantizer) => (
338 crate::openh264::OpenH264Rate::Quantizer(quantizer),
339 AppliedRateControl::Quantizer,
340 ),
341 None => (
342 crate::openh264::OpenH264Rate::Bitrate {
343 target: rate.bitrate,
344 constant: rate.mode == BitrateMode::Constant,
345 },
346 AppliedRateControl::Bitrate,
347 ),
348 }
349}
350
351pub(crate) fn quantizer_request(
354 encoder_name: &str,
355 rate: VideoRateControl,
356) -> Option<Result<MechanismRequest, String>> {
357 let quantizer = rate.quantizer?;
358 Some(
359 mechanism_request(
360 encoder_name,
361 RateControlMechanism::ConstantQuantizer,
362 rate.bitrate,
363 quantizer,
364 )
365 .ok_or_else(|| format!("{encoder_name} has no constant-quantizer option alpha2 knows")),
366 )
367}