use bytes::Bytes;
use moq_net::Timestamp;
use super::decoder::{Config, Kind};
use crate::Error;
use crate::frame::Frame;
mod openh264;
#[cfg(target_os = "macos")]
mod videotoolbox;
#[cfg(target_os = "windows")]
mod mediafoundation;
#[cfg(all(target_os = "linux", feature = "nvdec"))]
mod nvdec;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Codec {
H264,
H265,
Av1,
}
impl Codec {
fn label(self) -> &'static str {
match self {
Codec::H264 => "H.264",
Codec::H265 => "H.265",
Codec::Av1 => "AV1",
}
}
}
pub(crate) struct Decoded {
pub timestamp: Timestamp,
pub frame: Frame,
}
pub(crate) trait Backend: Send {
fn decode(&mut self, access_unit: Bytes, timestamp: Timestamp, keyframe: bool) -> Result<Vec<Decoded>, Error>;
fn name(&self) -> &str;
}
type Open = fn(Codec, &Config) -> Result<Box<dyn Backend>, Error>;
struct Candidate {
name: &'static str,
supports: fn(Codec) -> bool,
open: Open,
}
const HARDWARE: &[Candidate] = &[
#[cfg(target_os = "macos")]
Candidate {
name: videotoolbox::NAME,
supports: |c| matches!(c, Codec::H264 | Codec::H265),
open: videotoolbox::VideoToolbox::open,
},
#[cfg(target_os = "windows")]
Candidate {
name: mediafoundation::NAME,
supports: |c| matches!(c, Codec::H264 | Codec::H265),
open: mediafoundation::MediaFoundation::open,
},
#[cfg(all(target_os = "linux", feature = "nvdec"))]
Candidate {
name: nvdec::NAME,
supports: |c| matches!(c, Codec::H264 | Codec::H265 | Codec::Av1),
open: nvdec::Nvdec::open,
},
];
const SOFTWARE: Candidate = Candidate {
name: openh264::NAME,
supports: |c| matches!(c, Codec::H264),
open: openh264::Openh264::open,
};
pub(crate) fn open(codec: Codec, config: &Config) -> Result<Box<dyn Backend>, Error> {
let candidates: Vec<&Candidate> = match &config.kind {
Kind::Auto => HARDWARE.iter().chain(std::iter::once(&SOFTWARE)).collect(),
Kind::Hardware => HARDWARE.iter().collect(),
Kind::Software => vec![&SOFTWARE],
Kind::Named(name) => {
let all = HARDWARE.iter().chain(std::iter::once(&SOFTWARE));
all.filter(|c| c.name == name).collect()
}
};
let mut tried = Vec::new();
for candidate in candidates {
if !(candidate.supports)(codec) {
continue;
}
tried.push(candidate.name);
match (candidate.open)(codec, config) {
Ok(backend) => return Ok(backend),
Err(e) => tracing::debug!(decoder = candidate.name, error = %e, "decoder unavailable, trying next"),
}
}
if tried.is_empty() {
return Err(Error::NoDecoder(format!("none support {}", codec.label())));
}
Err(Error::NoDecoder(tried.join(", ")))
}