use std::ffi::CString;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum HardwareEncoder {
#[default]
Auto,
None,
Nvenc,
Qsv,
Amf,
VideoToolbox,
Vaapi,
}
impl HardwareEncoder {
#[must_use]
pub fn available() -> &'static [Self] {
static AVAILABLE: OnceLock<Vec<HardwareEncoder>> = OnceLock::new();
AVAILABLE.get_or_init(|| {
let mut result = Vec::new();
if Self::Nvenc.is_available() {
result.push(Self::Nvenc);
}
if Self::Qsv.is_available() {
result.push(Self::Qsv);
}
if Self::Amf.is_available() {
result.push(Self::Amf);
}
if Self::VideoToolbox.is_available() {
result.push(Self::VideoToolbox);
}
if Self::Vaapi.is_available() {
result.push(Self::Vaapi);
}
result
})
}
#[must_use]
pub fn is_available(self) -> bool {
match self {
Self::Auto | Self::None => true,
Self::Nvenc => is_encoder_available("h264_nvenc") || is_encoder_available("hevc_nvenc"),
Self::Qsv => is_encoder_available("h264_qsv") || is_encoder_available("hevc_qsv"),
Self::Amf => is_encoder_available("h264_amf") || is_encoder_available("hevc_amf"),
Self::VideoToolbox => {
is_encoder_available("h264_videotoolbox")
|| is_encoder_available("hevc_videotoolbox")
}
Self::Vaapi => is_encoder_available("h264_vaapi") || is_encoder_available("hevc_vaapi"),
}
}
}
fn is_encoder_available(name: &str) -> bool {
unsafe {
ff_sys::ensure_initialized();
let Ok(c_name) = CString::new(name) else {
return false;
};
ff_sys::avcodec::find_encoder_by_name(c_name.as_ptr()).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_hardware_encoder() {
assert_eq!(HardwareEncoder::default(), HardwareEncoder::Auto);
}
#[test]
fn test_auto_and_none_always_available() {
assert!(HardwareEncoder::Auto.is_available());
assert!(HardwareEncoder::None.is_available());
}
#[test]
fn available_should_contain_only_hardware_backends() {
let available = HardwareEncoder::available();
assert!(
!available.contains(&HardwareEncoder::Auto),
"Auto is a control variant, not a hardware backend"
);
assert!(
!available.contains(&HardwareEncoder::None),
"None is a control variant, not a hardware backend"
);
}
#[test]
fn test_hardware_encoder_availability() {
let _nvenc = HardwareEncoder::Nvenc.is_available();
let _qsv = HardwareEncoder::Qsv.is_available();
let _amf = HardwareEncoder::Amf.is_available();
let _videotoolbox = HardwareEncoder::VideoToolbox.is_available();
let _vaapi = HardwareEncoder::Vaapi.is_available();
}
}