use alpha2_media::{BitrateMode, VideoRateControl};
use rsmpeg::avutil::AVDictionary;
use std::ffi::CStr;
pub(crate) fn constant_mode_option(encoder_name: &str) -> Option<(&'static CStr, &'static CStr)> {
if encoder_name.ends_with("_vulkan") {
Some((c"rc_mode", c"cbr"))
} else if encoder_name.ends_with("_nvenc") || encoder_name.ends_with("_amf") {
Some((c"rc", c"cbr"))
} else if encoder_name.ends_with("_vaapi") || encoder_name.ends_with("_d3d12va") {
Some((c"rc_mode", c"CBR"))
} else if encoder_name.ends_with("_mf") {
Some((c"rate_control", c"cbr"))
} else if encoder_name.ends_with("_videotoolbox") {
Some((c"constant_bit_rate", c"1"))
} else {
None
}
}
pub(crate) fn constant_rate_hint(encoder_name: &str, rate: VideoRateControl) -> &'static str {
if rate.mode != BitrateMode::Constant {
""
} else if encoder_name.ends_with("_videotoolbox") {
" (a constant bit rate was asked for, which VideoToolbox has only on macOS 13 and later)"
} else if encoder_name.ends_with("_d3d12va") {
" (a constant bit rate was asked for, which D3D12 Video Encode has only on some devices; \
ask for a quantizer instead)"
} else {
""
}
}
pub(crate) fn rate_control_options(
encoder_name: &str,
rate: VideoRateControl,
options: AVDictionary,
) -> AVDictionary {
match rate.mode {
BitrateMode::Variable => options,
BitrateMode::Constant => {
match mechanism_request(
encoder_name,
RateControlMechanism::ConstantBitrate,
rate.bitrate,
0,
) {
Some(request) => request.dictionary(options),
None => options,
}
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum RateControlMechanism {
ConstantBitrate,
VariableBitrate,
ConstantQuantizer,
}
impl RateControlMechanism {
pub const ALL: [Self; 3] = [
Self::ConstantBitrate,
Self::VariableBitrate,
Self::ConstantQuantizer,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ConstantBitrate => "cbr",
Self::VariableBitrate => "vbr",
Self::ConstantQuantizer => "cqp",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct MechanismRequest {
pub options: Vec<(&'static CStr, String)>,
pub bit_rate: i64,
}
impl MechanismRequest {
pub fn describe(&self) -> String {
let mut parts = self
.options
.iter()
.map(|(key, value)| format!("{}={value}", key.to_string_lossy()))
.collect::<Vec<_>>();
if self.bit_rate > 0 {
parts.insert(0, format!("b={}", self.bit_rate));
}
parts.join(" ")
}
pub fn dictionary(&self, mut options: AVDictionary) -> AVDictionary {
for (key, value) in &self.options {
options = crate::convert::set_encoder_option(options, key, value);
}
options
}
}
pub(crate) fn mechanism_request(
encoder_name: &str,
mechanism: RateControlMechanism,
bitrate: u64,
quantizer: u32,
) -> Option<MechanismRequest> {
let target = i64::try_from(bitrate).unwrap_or(i64::MAX);
let constant = |switch: Option<(&'static CStr, &'static str)>| {
let mut options = vec![
(c"maxrate", bitrate.to_string()),
(c"minrate", bitrate.to_string()),
(c"bufsize", bitrate.to_string()),
];
if let Some((key, value)) = switch {
options.push((key, value.to_owned()));
}
MechanismRequest {
options,
bit_rate: target,
}
};
let switched = |key: &'static CStr, value: &str| MechanismRequest {
options: vec![(key, value.to_owned())],
bit_rate: target,
};
let fixed_quantizer = |options: Vec<(&'static CStr, String)>| MechanismRequest {
options,
bit_rate: 0,
};
let q = quantizer.to_string();
let family = if encoder_name.ends_with("_vulkan") {
"vulkan"
} else if encoder_name.ends_with("_nvenc") {
"nvenc"
} else if encoder_name.ends_with("_vaapi") {
"vaapi"
} else if encoder_name.ends_with("_d3d12va") {
"d3d12va"
} else if encoder_name.ends_with("_amf") {
"amf"
} else if encoder_name.ends_with("_mf") {
"mf"
} else if encoder_name.ends_with("_videotoolbox") {
"videotoolbox"
} else if encoder_name.ends_with("_qsv") {
"qsv"
} else if matches!(encoder_name, "libx264" | "libx265") {
"x26x"
} else if matches!(
encoder_name,
"libvpx" | "libvpx-vp9" | "libaom-av1" | "libsvtav1"
) {
"crf"
} else {
"libavcodec"
};
Some(match (family, mechanism) {
("crf", RateControlMechanism::ConstantBitrate) if encoder_name == "libsvtav1" => {
MechanismRequest {
options: vec![
(c"bufsize", bitrate.to_string()),
(c"svtav1-params", "rc=2:pred-struct=1".to_owned()),
],
bit_rate: target,
}
}
(_, RateControlMechanism::ConstantBitrate) => constant(
constant_mode_option(encoder_name)
.map(|(key, value)| (key, value.to_str().unwrap_or_default())),
),
("vulkan", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "vbr"),
("nvenc", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr"),
("vaapi" | "d3d12va", RateControlMechanism::VariableBitrate) => switched(c"rc_mode", "VBR"),
("amf", RateControlMechanism::VariableBitrate) => switched(c"rc", "vbr_peak"),
("mf", RateControlMechanism::VariableBitrate) => switched(c"rate_control", "u_vbr"),
(_, RateControlMechanism::VariableBitrate) => MechanismRequest {
options: Vec::new(),
bit_rate: target,
},
("vulkan", RateControlMechanism::ConstantQuantizer) => {
fixed_quantizer(vec![(c"rc_mode", "cqp".to_owned()), (c"qp", q)])
}
("nvenc", RateControlMechanism::ConstantQuantizer) => {
fixed_quantizer(vec![(c"rc", "constqp".to_owned()), (c"qp", q)])
}
("vaapi" | "d3d12va", RateControlMechanism::ConstantQuantizer) => {
fixed_quantizer(vec![(c"rc_mode", "CQP".to_owned()), (c"qp", q)])
}
("amf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![
(c"rc", "cqp".to_owned()),
(c"qp_i", q.clone()),
(c"qp_p", q),
]),
("x26x", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"qp", q)]),
("crf", RateControlMechanism::ConstantQuantizer) => fixed_quantizer(vec![(c"crf", q)]),
_ => return None,
})
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum AppliedRateControl {
Quantizer,
Bitrate,
}
impl AppliedRateControl {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Quantizer => "quantizer",
Self::Bitrate => "bitrate",
}
}
}
pub(crate) fn quantizer_request(
encoder_name: &str,
rate: VideoRateControl,
) -> Option<Result<MechanismRequest, String>> {
let quantizer = rate.quantizer?;
Some(
mechanism_request(
encoder_name,
RateControlMechanism::ConstantQuantizer,
rate.bitrate,
quantizer,
)
.ok_or_else(|| format!("{encoder_name} has no constant-quantizer option alpha2 knows")),
)
}