use std::path::{Path, PathBuf};
use crate::annexb::Codec;
use crate::v4l2;
const DEV_DIR: &str = "/dev";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Asset {
pub codec: Codec,
pub path: PathBuf,
}
impl Asset {
pub fn h264(path: impl Into<PathBuf>) -> Self {
Self {
codec: Codec::H264,
path: path.into(),
}
}
pub fn h265(path: impl Into<PathBuf>) -> Self {
Self {
codec: Codec::H265,
path: path.into(),
}
}
}
#[derive(Clone, Debug)]
pub struct DecoderInfo {
pub path: PathBuf,
pub driver: String,
pub h264: bool,
pub hevc: bool,
pub stateful: bool,
}
#[derive(Clone, Debug, Default)]
pub struct Decoders {
pub found: Vec<DecoderInfo>,
}
impl Decoders {
pub fn detect() -> Self {
Self::detect_in(DEV_DIR)
}
pub fn detect_in(dev: impl AsRef<Path>) -> Self {
let mut found = Vec::new();
let Ok(entries) = std::fs::read_dir(dev.as_ref()) else {
return Self { found };
};
let mut paths: Vec<PathBuf> = entries
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| {
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with("video") && n[5..].parse::<u32>().is_ok())
})
.collect();
paths.sort();
for path in paths {
if let Some(info) = Self::inspect(&path) {
found.push(info);
}
}
Self { found }
}
fn inspect(path: &Path) -> Option<DecoderInfo> {
use std::os::fd::AsFd;
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path)
.ok()?;
let fd = file.as_fd();
let cap = v4l2::querycap(fd).ok()?;
let caps = cap.caps();
if caps & v4l2::CAP_VIDEO_M2M_MPLANE == 0 || caps & v4l2::CAP_STREAMING == 0 {
return None;
}
let (mut h264, mut hevc, mut any_compressed) = (false, false, false);
for index in 0.. {
match v4l2::enum_fmt(fd, v4l2::BUF_TYPE_OUTPUT_MPLANE, index).ok()? {
None => break,
Some(desc) => {
if desc.flags & v4l2::FMT_FLAG_COMPRESSED != 0 {
any_compressed = true;
}
match desc.pixelformat {
v4l2::PIX_FMT_H264 => h264 = true,
v4l2::PIX_FMT_HEVC => hevc = true,
_ => {}
}
}
}
}
if !any_compressed {
return None;
}
let driver = cap.driver_name().to_owned();
let stateful = !matches!(
driver.as_str(),
"rpivid" | "hantro-vpu" | "rkvdec" | "cedrus"
);
Some(DecoderInfo {
path: path.to_path_buf(),
driver,
h264,
hevc,
stateful,
})
}
pub fn stateful_for(&self, codec: Codec) -> Option<&DecoderInfo> {
self.found.iter().find(|d| {
d.stateful
&& match codec {
Codec::H264 => d.h264,
Codec::H265 => d.hevc,
}
})
}
pub fn pick<'a>(&self, assets: &'a [Asset]) -> Option<(&'a Asset, &DecoderInfo)> {
assets
.iter()
.find_map(|asset| self.stateful_for(asset.codec).map(|d| (asset, d)))
}
}