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