use anyhow::Result;
use nokhwa::pixel_format::RgbFormat;
use nokhwa::utils::{
CameraFormat, CameraIndex, FrameFormat, RequestedFormat, RequestedFormatType, Resolution,
};
use nokhwa::Camera as NokhwaCamera;
#[derive(Debug, Clone)]
pub struct CameraInfo {
pub index: u32,
pub name: String,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct Frame {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
pub timestamp_us: u64,
}
impl Frame {
pub fn to_jpeg(&self, quality: u8) -> Result<Vec<u8>> {
use image::{ImageBuffer, Rgb};
let img: ImageBuffer<Rgb<u8>, _> =
ImageBuffer::from_raw(self.width, self.height, self.data.clone())
.ok_or_else(|| anyhow::anyhow!("Failed to create image buffer"))?;
let mut jpeg_data = Vec::new();
let mut encoder =
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg_data, quality);
encoder.encode_image(&img)?;
Ok(jpeg_data)
}
pub fn from_jpeg(jpeg_data: &[u8]) -> Result<Self> {
use image::ImageReader;
use std::io::Cursor;
let img = ImageReader::new(Cursor::new(jpeg_data))
.with_guessed_format()?
.decode()?
.to_rgb8();
Ok(Frame {
width: img.width(),
height: img.height(),
data: img.into_raw(),
timestamp_us: 0,
})
}
}
pub struct Camera {
inner: NokhwaCamera,
start_time: std::time::Instant,
}
impl Camera {
pub fn open(index: u32, width: u32, height: u32, fps: u32) -> Result<Self> {
let camera_index = CameraIndex::Index(index);
let format = CameraFormat::new(Resolution::new(width, height), FrameFormat::MJPEG, fps);
let requested = RequestedFormat::new::<RgbFormat>(RequestedFormatType::Closest(format));
let camera_result = NokhwaCamera::new(camera_index.clone(), requested);
let mut camera = match camera_result {
Ok(cam) => cam,
Err(_) => {
let fallback = RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);
NokhwaCamera::new(camera_index, fallback)?
}
};
camera.open_stream()?;
Ok(Camera {
inner: camera,
start_time: std::time::Instant::now(),
})
}
pub fn width(&self) -> u32 {
self.inner.resolution().width()
}
pub fn height(&self) -> u32 {
self.inner.resolution().height()
}
pub fn fps(&self) -> u32 {
self.inner.frame_rate()
}
pub fn capture(&mut self) -> Result<Frame> {
let frame = self.inner.frame()?;
let decoded = frame.decode_image::<RgbFormat>()?;
let timestamp_us = self.start_time.elapsed().as_micros() as u64;
Ok(Frame {
width: decoded.width(),
height: decoded.height(),
data: decoded.into_raw(),
timestamp_us,
})
}
pub fn stop(&mut self) -> Result<()> {
self.inner.stop_stream()?;
Ok(())
}
}
impl Drop for Camera {
fn drop(&mut self) {
let _ = self.inner.stop_stream();
}
}
pub fn list_cameras() -> Result<Vec<CameraInfo>> {
use nokhwa::utils::CameraInfo as NokhwaCameraInfo;
let cameras = nokhwa::query(nokhwa::utils::ApiBackend::Auto)?;
Ok(cameras
.into_iter()
.map(|c: NokhwaCameraInfo| CameraInfo {
index: match c.index() {
CameraIndex::Index(i) => *i,
CameraIndex::String(_) => 0,
},
name: c.human_name().to_string(),
description: c.description().to_string(),
})
.collect())
}