use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use eframe::egui;
use image::RgbImage;
use nokhwa::pixel_format::RgbFormat;
use nokhwa::utils::{ApiBackend, CameraIndex, RequestedFormat, RequestedFormatType};
use nokhwa::{Camera, query};
const MAX_CONSECUTIVE_ERRORS: u32 = 30;
#[derive(Clone)]
pub struct CameraOption {
pub index: CameraIndex,
pub name: String,
}
pub fn list_cameras() -> anyhow::Result<Vec<CameraOption>> {
let infos = query(ApiBackend::Auto)?;
Ok(infos
.into_iter()
.map(|info| CameraOption {
name: info.human_name(),
index: info.index().clone(),
})
.collect())
}
pub type FrameSlot = Arc<Mutex<Option<RgbImage>>>;
pub struct CaptureHandle {
join: Option<JoinHandle<()>>,
stop: Arc<AtomicBool>,
slot: FrameSlot,
error: Arc<Mutex<Option<String>>>,
}
impl CaptureHandle {
pub fn stop(&mut self) {
self.stop.store(true, Ordering::Release);
if let Some(handle) = self.join.take() {
let _ = handle.join();
}
}
pub fn latest_frame(&self) -> Option<RgbImage> {
self.slot.lock().unwrap_or_else(|e| e.into_inner()).take()
}
pub fn take_error(&self) -> Option<String> {
self.error.lock().unwrap_or_else(|e| e.into_inner()).take()
}
}
impl Drop for CaptureHandle {
fn drop(&mut self) {
self.stop();
}
}
pub fn start_capture(index: CameraIndex, ctx: egui::Context) -> CaptureHandle {
let stop = Arc::new(AtomicBool::new(false));
let slot: FrameSlot = Arc::new(Mutex::new(None));
let error = Arc::new(Mutex::new(None));
let thread_stop = Arc::clone(&stop);
let thread_slot = Arc::clone(&slot);
let thread_error = Arc::clone(&error);
let join = std::thread::Builder::new()
.name("camshooter-capture".into())
.spawn(move || capture_loop(index, ctx, thread_stop, thread_slot, thread_error))
.expect("failed to spawn capture thread");
CaptureHandle {
join: Some(join),
stop,
slot,
error,
}
}
fn capture_loop(
index: CameraIndex,
ctx: egui::Context,
stop: Arc<AtomicBool>,
slot: FrameSlot,
error: Arc<Mutex<Option<String>>>,
) {
let requested =
RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);
let mut camera = match Camera::new(index, requested) {
Ok(c) => c,
Err(e) => {
*error.lock().unwrap_or_else(|e| e.into_inner()) =
Some(format!("Could not open camera: {e}"));
ctx.request_repaint();
return;
}
};
if let Err(e) = camera.open_stream() {
*error.lock().unwrap_or_else(|e| e.into_inner()) =
Some(format!("Could not start stream: {e}"));
ctx.request_repaint();
return;
}
let mut consecutive_errors = 0u32;
while !stop.load(Ordering::Acquire) {
match camera
.frame()
.and_then(|buf| buf.decode_image::<RgbFormat>())
{
Ok(rgb) => {
consecutive_errors = 0;
*slot.lock().unwrap_or_else(|e| e.into_inner()) = Some(rgb);
ctx.request_repaint();
}
Err(_) => {
consecutive_errors += 1;
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
*error.lock().unwrap_or_else(|e| e.into_inner()) =
Some("Lost connection to the camera (was it unplugged?)".into());
ctx.request_repaint();
break;
}
}
}
}
}