summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs206
1 files changed, 7 insertions, 199 deletions
diff --git a/src/main.rs b/src/main.rs
index ab54508..5e9ae13 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,56 +1,10 @@
-#![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 anyhow::Result;
use sshcamera::v4l2::{Device as V4l2, Field};
-use sshcamera::color::yuv2rgb;
-use jpeg_decoder::{self as jpeg, Decoder as JpegDec};
-
-struct SignalChannel{
- waker: Option<Waker>,
- 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<Mutex<Self>>, 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
- }
- }
-}
+use sshcamera::v4l2cairo::V4l2Cairo;
+use sshcamera::gtk;
+use gtk4::glib::ExitCode;
-#[derive(Clone)]
-struct AppState{
- frame_buf: Arc<Mutex<Option<cairo::ImageSurfaceDataOwned>>>,
- notify: Arc<Mutex<SignalChannel>>,
- fbpool: Arc<Mutex<Vec<cairo::ImageSurfaceDataOwned>>>,
-}
-
-fn videothread(apps: AppState) -> Result<()>{
+fn main() -> Result<ExitCode>{
let v = V4l2::open("/dev/video0")?;
// TODO: It should be better.
@@ -61,152 +15,6 @@ fn videothread(apps: AppState) -> Result<()>{
.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<cairo::ImageSurface> = c.next(|frame, _|{
- let mut img = None;
- let mut fbpool = apps.fbpool.lock().unwrap();
- while let Some(i) = fbpool.pop(){
- let i = i.into_inner();
- if i.width() as usize == w && i.height() as usize == h{
- img = Some(i);
- break;
- }
- }
- drop(fbpool);
- let mut img = match img{
- Some(i) => i,
- None => {
- 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;
- imgslice[stride*y + x*4 + 3] = 0;
- }
- 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 + 2];
- imgslice[stride*y + x*4 + 1] = b[(y*w+x)*3 + 1];
- imgslice[stride*y + x*4 + 2] = b[(y*w+x)*3 + 0];
- imgslice[stride*y + x*4 + 3] = 0;
- }
-
- 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: &gtk::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: Option<cairo::ImageSurface> = None;
- draw.set_draw_func(clone!{
- #[strong(rename_to=frame_buf)] apps.frame_buf,
- #[strong(rename_to=fbpool)] apps.fbpool,
- 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(){
- if let Some(mut lastframe) = frame_cache.take(){
- let mut fbpool = fbpool.lock().unwrap();
- if fbpool.len() < 8{
- fbpool.push(lastframe.take_data().unwrap());
- }
- }
- frame_cache = Some(newfb.into_inner());
- }
- if let Some(image) = frame_cache.clone(){
- 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(&notify, ctx){
- Poll::Ready(_) => {
- draw.queue_draw();
- },
- pending => return pending,
- }
- }
- }
- }));
-
- let win = gtk::ApplicationWindow::builder()
- .application(app)
- .child(&draw)
- .build();
- win.present();
-}
-
-fn main() -> Result<glib::ExitCode>{
- let app = gtk::Application::builder()
- .build();
- app.connect_activate(gtkmain);
- Ok(app.run())
+ let v2c = V4l2Cairo::new(c);
+ gtk::main(v2c)
}