#![allow(unused)] use anyhow::{anyhow, Result}; use std::sync::{Arc, Mutex}; use std::future::poll_fn; use std::task::{Context, Poll, Waker}; use std::thread; use gtk4::{self as gtk, glib, cairo}; use gtk4::prelude::*; use glib::{clone, spawn_future_local}; use sshcamera::v4l2::{Device as V4l2, Field}; use sshcamera::color::yuv2rgb; use jpeg_decoder::{self as jpeg, Decoder as JpegDec}; struct SignalChannel{ waker: Option, active: bool, } impl SignalChannel{ fn new() -> Self{ Self{ waker: None, active: false, } } fn wake(&mut self){ self.active = true; if let Some(w) = self.waker.take(){ w.wake(); } } fn poll(this: &Arc>, ctx: &mut Context<'_>) -> Poll<()>{ let mut l = this.lock().unwrap(); if l.active{ l.active = false; Poll::Ready(()) }else{ l.waker = Some(ctx.waker().clone()); Poll::Pending } } } #[derive(Clone)] struct AppState{ frame_buf: Arc>>, notify: Arc>, fbpool: Arc>>, } fn videothread(apps: AppState) -> Result<()>{ let v = V4l2::open("/dev/video0")?; // TODO: It should be better. let c = v.captstream_builder()? .set_pixelformat("MJPG".into()) //.set_pixelformat("YUYV".into()) .set_field(Field::None) .build()?; assert!(["YUYV", "MJPG"].contains(&c.pixelformat().as_str())); assert!(c.field() == Field::None); let (w, h) = (c.width(), c.height()); let s = c.bytesperline(); loop{ let img: Result = c.next(|frame, _|{ let mut img = cairo::ImageSurface::create( cairo::Format::Rgb24, w.try_into()?, h.try_into()?)?; let stride: usize = img.stride().try_into()?; let mut imgslice = img.data()?; match c.pixelformat().as_str(){ "YUYV" => { for (x, y) in (0..h).map( |y| (0..w).map(move |x|(x, y))).flatten(){ let p = s*y + x*2; let (r, g, b) = yuv2rgb( frame[p], frame[p/4*4 + 1], frame[p/4*4 + 3]); imgslice[stride*y + x*4 + 0] = b; imgslice[stride*y + x*4 + 1] = g; imgslice[stride*y + x*4 + 2] = r; } drop(imgslice); Ok(img) }, "MJPG" => { // Jpeg is not placed in start of slice in some situation. // It is even possible that there are no Jpeg data. let jindex = (0..frame.len()-1) .filter(|i| frame[*i] == 0xff && frame[i+1] == 0xd8) .next() .ok_or(anyhow!("jpeg not found"))?; let mut jpeg = JpegDec::new(&frame[jindex..]); let b = jpeg.decode()?; let info = jpeg.info().unwrap(); assert!((info.width as usize, info.height as usize) == (w, h)); for (x, y) in (0..h).map( |y| (0..w).map(move |x|(x, y))).flatten(){ imgslice[stride*y + x*4 + 0] = b[(y*w+x)*3 + 0]; imgslice[stride*y + x*4 + 1] = b[(y*w+x)*3 + 1]; imgslice[stride*y + x*4 + 2] = b[(y*w+x)*3 + 2]; } drop(imgslice); Ok(img) }, _ => unreachable!(), } }).unwrap_or_else(|e| Err(e.into())); match img{ Ok(img) => { *apps.frame_buf.lock().unwrap() = Some(img.take_data().unwrap()); apps.notify.lock().unwrap().wake(); }, Err(err) => { println!("Skipping erroneous frame: {:?}", err); }, } } } fn gtkmain(app: >k::Application){ let apps = AppState{ frame_buf: Arc::new(Mutex::new(None)), notify: Arc::new(Mutex::new(SignalChannel::new())), fbpool: Arc::new(Mutex::new(Vec::new())), }; thread::spawn(clone!{ #[strong] apps, move || videothread(apps).unwrap() }); let draw = gtk::DrawingArea::new(); let mut frame_cache = None; draw.set_draw_func(clone!{ #[strong(rename_to=frame_buf)] apps.frame_buf, move |_draw, ctx, canvas_w, canvas_h|{ ctx.set_source_rgb(0., 0., 0.); ctx.paint().unwrap(); if let Some(newfb) = frame_buf.lock().unwrap().take(){ frame_cache = Some(newfb.into_inner()); } if let Some(image) = frame_cache.clone(){ //{ // let stride: usize = isurface.stride().try_into().unwrap(); // let mut isdata = isurface.data().unwrap(); // for y in 0..image.height{ // for x in 0..image.width{ // for c in 0..4{ // isdata[stride*y + x*4 + c] // = image.buf[(image.width*y+x)*4 + c]; // } // } // } //} let ipat = cairo::SurfacePattern::create(&image); let scale = ((canvas_w as f64) / (image.width() as f64)).min( (canvas_h as f64) / (image.height() as f64)); ctx.scale(scale, scale); ctx.set_source(&ipat).unwrap(); ctx.paint().unwrap(); } } }); spawn_future_local(poll_fn(clone!{ #[strong(rename_to=notify)] apps.notify, #[strong] draw, move |ctx|{ loop{ match SignalChannel::poll(¬ify, ctx){ Poll::Ready(_) => { draw.queue_draw(); }, pending => return pending, } } } })); let win = gtk::ApplicationWindow::builder() .application(app) .child(&draw) .build(); win.present(); } fn main() -> Result{ let app = gtk::Application::builder() .build(); app.connect_activate(gtkmain); Ok(app.run()) }