use super::encoder::{Codec, Config, Kind};
use crate::encode::Encoded;
use crate::{Error, Frame};
mod openh264;
#[cfg(target_os = "macos")]
mod videotoolbox;
#[cfg(target_os = "windows")]
mod mediafoundation;
#[cfg(all(target_os = "linux", feature = "nvenc"))]
mod nvenc;
#[cfg(all(target_os = "linux", feature = "vaapi"))]
mod vaapi;
pub(crate) trait Backend: Send {
fn encode(&mut self, frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error>;
fn finish(&mut self) -> Result<Vec<Encoded>, Error>;
fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error>;
fn name(&self) -> &str;
}
struct Candidate {
name: &'static str,
codecs: &'static [Codec],
open: fn(&Config) -> Result<Box<dyn Backend>, Error>,
}
const HARDWARE: &[Candidate] = &[
#[cfg(target_os = "macos")]
Candidate {
name: videotoolbox::NAME,
codecs: &[Codec::H264, Codec::H265],
open: videotoolbox::VideoToolbox::open,
},
#[cfg(target_os = "windows")]
Candidate {
name: mediafoundation::NAME,
codecs: &[Codec::H264, Codec::H265],
open: mediafoundation::MediaFoundation::open,
},
#[cfg(all(target_os = "linux", feature = "nvenc"))]
Candidate {
name: nvenc::NAME,
codecs: &[Codec::H264, Codec::H265],
open: nvenc::Nvenc::open,
},
#[cfg(all(target_os = "linux", feature = "vaapi"))]
Candidate {
name: vaapi::NAME,
codecs: &[Codec::H264],
open: vaapi::Vaapi::open,
},
];
const SOFTWARE: &[Candidate] = &[Candidate {
name: openh264::NAME,
codecs: &[Codec::H264],
open: openh264::Openh264::open,
}];
pub(crate) fn open(config: &Config) -> Result<Box<dyn Backend>, Error> {
let codec = config.codec;
let supports = move |c: &&Candidate| c.codecs.contains(&codec);
let hardware = HARDWARE.iter().filter(supports);
let software = SOFTWARE.iter().filter(supports);
let candidates: Vec<&Candidate> = match &config.kind {
Kind::Auto => hardware.chain(software).collect(),
Kind::Hardware => hardware.collect(),
Kind::Software => software.collect(),
Kind::Named(name) => HARDWARE
.iter()
.chain(SOFTWARE.iter())
.filter(supports)
.filter(|c| c.name == name)
.collect(),
};
let mut tried = Vec::new();
for candidate in candidates {
tried.push(candidate.name);
match (candidate.open)(config) {
Ok(backend) => return Ok(backend),
Err(e) => tracing::debug!(encoder = candidate.name, error = %e, "encoder unavailable, trying next"),
}
}
Err(Error::NoEncoder(tried.join(", ")))
}
#[cfg(test)]
pub(crate) mod test_util {
use h264_reader::nal::sps::SeqParameterSet;
use h264_reader::nal::{Nal, RefNal, UnitType};
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Described {
pub primaries: u8,
pub transfer: u8,
pub matrix: u8,
pub full_range: bool,
}
pub(crate) const BT601_DESCRIBED: Described = Described {
primaries: 6,
transfer: 1,
matrix: 6,
full_range: false,
};
pub(crate) const BT709_DESCRIBED: Described = Described {
primaries: 1,
transfer: 1,
matrix: 1,
full_range: false,
};
pub(crate) fn declared_color(annexb: &[u8]) -> Option<Described> {
let starts: Vec<usize> = (0..annexb.len().saturating_sub(2))
.filter(|&i| annexb[i..i + 3] == [0, 0, 1])
.map(|i| i + 3)
.collect();
let sps = starts.iter().enumerate().find_map(|(n, &start)| {
let end = starts.get(n + 1).map_or(annexb.len(), |&next| next - 3);
let nal = RefNal::new(&annexb[start..end], &[], true);
match nal.header().ok()?.nal_unit_type() {
UnitType::SeqParameterSet => SeqParameterSet::from_bits(nal.rbsp_bits()).ok(),
_ => None,
}
})?;
let signal = sps.vui_parameters.as_ref()?.video_signal_type.as_ref()?;
let description = signal.colour_description.as_ref()?;
Some(Described {
primaries: description.colour_primaries,
transfer: description.transfer_characteristics,
matrix: description.matrix_coefficients,
full_range: signal.video_full_range_flag,
})
}
}