use std::path::{Path, PathBuf};
use edgefirst_v4l2::device::{self, Device};
use edgefirst_v4l2::queue::BufType;
use edgefirst_v4l2::uapi;
const ENV_DISABLE: &str = "EDGEFIRST_DISABLE_V4L2";
const ENV_DEVICE: &str = "EDGEFIRST_CODEC_V4L2_DEVICE";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApiVariant {
MultiPlanar,
SinglePlanar,
}
pub struct ProbedDevice {
pub dev: Device,
pub api: ApiVariant,
}
impl ProbedDevice {
pub fn path(&self) -> &Path {
self.dev.path()
}
}
pub fn probe() -> Option<ProbedDevice> {
if crate::jpeg::env_flag(ENV_DISABLE) {
log::debug!("v4l2 jpeg decode disabled via {ENV_DISABLE}");
return None;
}
for path in candidate_nodes() {
if let Some(dev) = probe_node(&path) {
log::info!(
"v4l2 jpeg decoder discovered at {} ({:?})",
dev.path().display(),
dev.api
);
return Some(dev);
}
}
log::debug!("no v4l2 jpeg decoder found; using cpu decoder");
None
}
fn candidate_nodes() -> Vec<PathBuf> {
if let Some(dev) = std::env::var_os(ENV_DEVICE) {
return vec![PathBuf::from(dev)];
}
device::enumerate()
.unwrap_or_default()
.into_iter()
.filter(|node| {
node.capabilities
.as_ref()
.is_ok_and(|c| c.is_m2m() && c.has_streaming())
})
.map(|node| node.path)
.collect()
}
fn probe_node(path: &Path) -> Option<ProbedDevice> {
let dev = Device::open(path).ok()?;
let caps = dev.capabilities();
if !caps.is_m2m() || !caps.has_streaming() {
return None;
}
let (api, output) = match caps.output_buf_type()? {
BufType::VideoOutputMplane => (ApiVariant::MultiPlanar, BufType::VideoOutputMplane),
_ => (ApiVariant::SinglePlanar, BufType::VideoOutput),
};
let has_jpeg = dev
.formats(output)
.ok()?
.iter()
.any(|f| f.fourcc == uapi::V4L2_PIX_FMT_JPEG || f.fourcc == uapi::V4L2_PIX_FMT_MJPEG);
has_jpeg.then_some(ProbedDevice { dev, api })
}