use std::fs::{File, OpenOptions};
use std::os::fd::AsRawFd;
use std::path::{Path, PathBuf};
use super::ioctl;
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 file: File,
pub api: ApiVariant,
pub path: PathBuf,
}
impl ProbedDevice {
pub fn fd(&self) -> std::os::fd::RawFd {
self.file.as_raw_fd()
}
}
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() {
match probe_node(&path) {
Some(dev) => {
log::info!(
"v4l2 jpeg decoder discovered at {} ({:?})",
dev.path.display(),
dev.api
);
return Some(dev);
}
None => continue,
}
}
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)];
}
let mut nodes: Vec<(u32, PathBuf)> = Vec::new();
let Ok(entries) = std::fs::read_dir("/dev") else {
return Vec::new();
};
for entry in entries.flatten() {
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
let Some(num) = name
.strip_prefix("video")
.and_then(|n| n.parse::<u32>().ok())
else {
continue;
};
nodes.push((num, entry.path()));
}
nodes.sort_by_key(|(num, _)| *num);
nodes.into_iter().map(|(_, path)| path).collect()
}
fn probe_node(path: &Path) -> Option<ProbedDevice> {
let file = OpenOptions::new().read(true).write(true).open(path).ok()?;
let fd = file.as_raw_fd();
let mut cap = ioctl::v4l2_capability::default();
unsafe { ioctl::vidioc_querycap(fd, &mut cap) }.ok()?;
let caps = cap.effective_caps();
if caps & ioctl::V4L2_CAP_STREAMING == 0 {
return None;
}
let api = if caps & ioctl::V4L2_CAP_VIDEO_M2M_MPLANE != 0 {
ApiVariant::MultiPlanar
} else if caps & ioctl::V4L2_CAP_VIDEO_M2M != 0 {
ApiVariant::SinglePlanar
} else {
return None;
};
if !output_queue_has_jpeg(fd, api) {
return None;
}
Some(ProbedDevice {
file,
api,
path: path.to_path_buf(),
})
}
fn output_queue_has_jpeg(fd: std::os::fd::RawFd, api: ApiVariant) -> bool {
let buf_type = match api {
ApiVariant::MultiPlanar => ioctl::V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
ApiVariant::SinglePlanar => ioctl::V4L2_BUF_TYPE_VIDEO_OUTPUT,
};
for index in 0..64u32 {
let mut desc = ioctl::v4l2_fmtdesc {
index,
type_: buf_type,
..Default::default()
};
if unsafe { ioctl::vidioc_enum_fmt(fd, &mut desc) }.is_err() {
break;
}
if desc.pixelformat == ioctl::V4L2_PIX_FMT_JPEG
|| desc.pixelformat == ioctl::V4L2_PIX_FMT_MJPEG
{
return true;
}
}
false
}