1use alpha2_media::{BitrateMode, VideoRateControl};
10use rsmpeg::avutil::AVDictionary;
11use std::ffi::CStr;
12
13pub(crate) fn constant_mode_option(encoder_name: &str) -> Option<(&'static CStr, &'static CStr)> {
22 if encoder_name.ends_with("_vulkan") {
23 Some((c"rc_mode", c"cbr"))
24 } else if encoder_name.ends_with("_nvenc") || encoder_name.ends_with("_amf") {
25 Some((c"rc", c"cbr"))
26 } else if encoder_name.ends_with("_vaapi") || encoder_name.ends_with("_d3d12va") {
27 Some((c"rc_mode", c"CBR"))
28 } else if encoder_name.ends_with("_mf") {
29 Some((c"rate_control", c"cbr"))
30 } else if encoder_name.ends_with("_videotoolbox") {
31 Some((c"constant_bit_rate", c"1"))
32 } else {
33 None
34 }
35}
36
37pub(crate) fn constant_rate_hint(encoder_name: &str, rate: VideoRateControl) -> &'static str {
43 if rate.mode != BitrateMode::Constant {
44 ""
45 } else if encoder_name.ends_with("_videotoolbox") {
46 " (a constant bit rate was asked for, which VideoToolbox has only on macOS 13 and later)"
47 } else if encoder_name.ends_with("_d3d12va") {
48 " (a constant bit rate was asked for, which D3D12 Video Encode has only on some devices; \
49 ask for a quantizer instead)"
50 } else {
51 ""
52 }
53}
54
55pub(crate) fn rate_control_options(
63 encoder_name: &str,
64 rate: VideoRateControl,
65 options: AVDictionary,
66) -> AVDictionary {
67 match rate.mode {
68 BitrateMode::Variable => options,
69 BitrateMode::Constant => {
70 match mechanism_request(
71 encoder_name,
72 RateControlMechanism::ConstantBitrate,
73 rate.bitrate,
74 0,
75 ) {
76 Some(request) => request.dictionary(options),
77 None => options,
78 }
79 }
80 }
81}
82
83#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
86#[non_exhaustive]
87pub enum RateControlMechanism {
88 ConstantBitrate,
90 VariableBitrate,
92 ConstantQuantizer,
94}
95
96impl RateControlMechanism {
97 pub const ALL: [Self; 3] = [
99 Self::ConstantBitrate,
100 Self::VariableBitrate,
101 Self::ConstantQuantizer,
102 ];
103
104 #[must_use]
106 pub const fn as_str(self) -> &'static str {
107 match self {
108 Self::ConstantBitrate => "cbr",
109 Self::VariableBitrate => "vbr",
110 Self::ConstantQuantizer => "cqp",
111 }
112 }
113}
114
115#[derive(Clone, Debug, Eq, PartialEq)]
117pub(crate) struct MechanismRequest {
118 pub options: Vec<(&'static CStr, String)>,
120 pub bit_rate: i64,
123}
124
125impl MechanismRequest {
126 pub fn describe(&self) -> String {
128 let mut parts = self
129 .options
130 .iter()
131 .map(|(key, value)| format!("{}={value}", key.to_string_lossy()))
132 .collect::<Vec<_>>();
133 if self.bit_rate > 0 {
134 parts.insert(0, format!("b={}", self.bit_rate));
135 }
136 parts.join(" ")
137 }
138
139 pub fn dictionary(&self, mut options: AVDictionary) -> AVDictionary {
141 for (key, value) in &self.options {
142 options = crate::convert::set_encoder_option(options, key, value);
143 }
144 options
145 }
146}
147
148pub(crate) fn mechanism_request(
156 encoder_name: &str,
157 mechanism: RateControlMechanism,
158 bitrate: u64,
159 quantizer: u32,
160) -> Option<MechanismRequest> {
161 let target = i64::try_from(bitrate).unwrap_or(i64::MAX);
162 let constant = |switch: Option<(&'static CStr, &'static str)>| {
163 let mut options = vec![
164 (c"maxrate", bitrate.to_string()),
165 (c"minrate", bitrate.to_string()),
166 (c"bufsize", bitrate.to_string()),
167 ];
168 if let Some((key, value)) = switch {
169 options.push((key, value.to_owned()));
170 }
171 MechanismRequest {
172 options,
173 bit_rate: target,
174 }
175 };
176 let switched = |key: &'static CStr, value: &str| MechanismRequest {
177 options: vec![(key, value.to_owned())],
178 bit_rate: target,
179 };
180 let fixed_quantizer = |options: Vec<(&'static CStr, String)>| MechanismRequest {
181 options,
182 bit_rate: 0,
183 };
184 let q = quantizer.to_string();
185 let family = if encoder_name.ends_with("_vulkan") {
186 "vulkan"
187 } else if encoder_name.ends_with("_nvenc") {
188 "nvenc"
189 } else if encoder_name.ends_with("_vaapi") {
190 "vaapi"
191 } else if encoder_name.ends_with("_d3d12va") {
192 "d3d12va"
193 } else if encoder_name.ends_with("_amf") {
194 "amf"
195 } else if encoder_name.ends_with("_mf") {
196 "mf"
197 } else if encoder_name.ends_with("_videotoolbox") {
198 "videotoolbox"
199 } else if encoder_name.ends_with("_qsv") {
200 "qsv"
201 } else if matches!(encoder_name, "libx264" | "libx265") {
202 "x26x"
203 } else if matches!(
204 encoder_name,
205 "libvpx" | "libvpx-vp9" | "libaom-av1" | "libsvtav1"
206 ) {
207 "crf"
208 } else {
209 "libavcodec"
210 };
211 Some(match (family, mechanism) {
212 ("crf", RateControlMechanism::ConstantBitrate) if encoder_name == "libsvtav1" => {
216 MechanismRequest {
217 options: vec![
218 (c"bufsize", bitrate.to_string()),
219 (c"svtav1-params", "rc=2:pred-struct=1".to_owned()),
220 ],
221 bit_rate: target,
222 }
223 }
224 (_, RateControlMechanism::ConstantBitrate) => constant(
225 constant_mode_option(encoder_name)
226 .map(|(key, value)| (key, value.to_str().unwrap_or_default())),
227 ),
228 ("vulkan", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "vbr"),
229 ("nvenc", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr"),
230 ("vaapi" | "d3d12va", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "VBR"),
231 ("amf", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr_peak"),
232 ("mf", RateControlMechanism::VariableBitrate) => switched(c"rate_control", "u_vbr"),
233 (_, RateControlMechanism::VariableBitrate) => MechanismRequest {
234 options: Vec::new(),
235 bit_rate: target,
236 },
237 ("vulkan", RateControlMechanism::ConstantQuantizer) => {
238 fixed_quantizer(vec![(c"rc_mode", "cqp".to_owned()), (c"qp", q)])
239 }
240 ("nvenc", RateControlMechanism::ConstantQuantizer) => {
241 fixed_quantizer(vec![(c"rc", "constqp".to_owned()), (c"qp", q)])
242 }
243 ("vaapi" | "d3d12va", RateControlMechanism::ConstantQuantizer) => {
247 fixed_quantizer(vec![(c"rc_mode", "CQP".to_owned()), (c"qp", q)])
248 }
249 ("amf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![
250 (c"rc", "cqp".to_owned()),
251 (c"qp_i", q.clone()),
252 (c"qp_p", q),
253 ]),
254 ("x26x", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"qp", q)]),
257 ("crf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"crf", q)]),
258 _ => return None,
264 })
265}
266
267#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
269#[non_exhaustive]
270pub enum AppliedRateControl {
271 Quantizer,
273 Bitrate,
275}
276
277impl AppliedRateControl {
278 #[must_use]
280 pub const fn as_str(self) -> &'static str {
281 match self {
282 Self::Quantizer => "quantizer",
283 Self::Bitrate => "bitrate",
284 }
285 }
286}
287
288pub(crate) fn quantizer_request(
291 encoder_name: &str,
292 rate: VideoRateControl,
293) -> Option<Result<MechanismRequest, String>> {
294 let quantizer = rate.quantizer?;
295 Some(
296 mechanism_request(
297 encoder_name,
298 RateControlMechanism::ConstantQuantizer,
299 rate.bitrate,
300 quantizer,
301 )
302 .ok_or_else(|| format!("{encoder_name} has no constant-quantizer option alpha2 knows")),
303 )
304}