use std::{sync::Arc, time::Duration};
use bevy_v4l2::{
ColorTap,
capture::{Capture, CaptureConfig, FOURCC_MJPG},
select::{CameraFormat, FrameFormat, RequestedFormat, choose},
};
use super::{
devices::CameraDevice,
video::{Pixels, VideoFrame, VideoSource},
};
pub fn cameras() -> Vec<CameraDevice> {
bevy_v4l2::controls::devices()
.into_iter()
.enumerate()
.map(|(i, info)| {
let detail = bevy_v4l2::capture::list_modes(&info.path)
.ok()
.and_then(|modes| choose(&modes, RequestedFormat::HighestResolution))
.map(|c| format!("{}x{} {} @ {:.0} fps", c.width, c.height, c.fourcc, c.fps))
.unwrap_or_else(|| "no usable mode".into());
CameraDevice {
id: info.path.to_string_lossy().into_owned(),
name: info.name,
is_default: i == 0,
detail,
}
})
.collect()
}
pub struct Camera {
capture: Arc<Capture>,
tap: ColorTap,
_decoder: Option<bevy_v4l2::mjpeg::Decoder>,
raw: bool,
}
impl Camera {
pub fn open(id: Option<&str>, width: u32, height: u32, fps: f32) -> Result<Self, String> {
let path = match id {
Some(id) => std::path::PathBuf::from(id),
None => bevy_v4l2::controls::devices()
.into_iter()
.next()
.map(|d| d.path)
.ok_or("no camera")?,
};
let modes = bevy_v4l2::capture::list_modes(&path)
.map_err(|e| format!("cannot enumerate {}: {e}", path.display()))?;
let want = CameraFormat::new(width, height, FrameFormat::Any, fps.round() as u32);
let choice = choose(&modes, RequestedFormat::Closest(want))
.ok_or_else(|| format!("{} offers nothing usable", path.display()))?;
let config = CaptureConfig {
device: path.clone(),
width: choice.width,
height: choice.height,
fourcc: choice.fourcc,
fps: Some(choice.fps.round() as u32),
..Default::default()
};
let capture = Arc::new(Capture::open(&config).map_err(|e| format!("camera: {e}"))?);
let layout = capture.layout();
let raw = layout.fourcc != FOURCC_MJPG;
tracing::info!(
"bevy_iroh: camera {} at {}x{} {} @ {:.0} fps ({})",
path.display(),
layout.width,
layout.height,
layout.fourcc,
choice.fps,
if raw { "raw" } else { "MJPEG" }
);
let tap = capture.color_tap();
let decoder = (!raw).then(|| {
bevy_v4l2::mjpeg::Decoder::start(capture.clone(), layout.width, layout.height)
});
Ok(Self {
capture,
tap,
_decoder: decoder,
raw,
})
}
}
impl VideoSource for Camera {
fn next_frame(&mut self) -> Option<VideoFrame> {
if self.raw {
while let Some(frame) = self.capture.try_recv_latest() {
self.capture.requeue(frame.index);
}
}
let frame = self.tap.recv_timeout(Duration::from_millis(5))?;
let frame = std::iter::from_fn(|| self.tap.try_recv_latest())
.last()
.unwrap_or(frame);
Some(yuv_to_frame(frame))
}
}
fn yuv_to_frame(frame: bevy_v4l2::capture::YuvFrame) -> VideoFrame {
VideoFrame {
width: frame.width,
height: frame.height,
pixels: Pixels::I420 {
y: frame.y,
u: frame.u,
v: frame.v,
},
timestamp_ms: frame.timestamp.as_millis() as u64,
}
}
impl VideoSource for ColorTap {
fn next_frame(&mut self) -> Option<VideoFrame> {
self.try_recv_latest().map(yuv_to_frame)
}
}