1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
/* Copyright (C) 2024 DorotaC
* SPDX-License-Identifier: MIT OR Apache-2.0
*/
/*! Real-life example: using a stream in a rendering thread.
*
* This example demonstrates using the GPU to draw camera frames with zero copies.
* This example uses the "next" (simpler) API in multiple threads, with polling for readiness.
*
* The difficulty of this example is that there are two event loops: one for the display and one fetching frames from the camera.
* The display loop cannot simply block waiting for the camera because it's got other events to handle.
* The camera can't simply draw to the screen because it is not available until the other loop is ready.
*
* So instead, the camera loop waits for frames to be ready and notifies the display loop. A reference to the camera stream handle is borrowed and sent to the display loop every time a new camera frame arrives.
*/
use std::error::Error;
use std::io;
use std::os::fd::AsFd;
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Instant;
use glium::Surface;
use vidi::{CaptureStream, StreamBorrowing, Config, FourCC};
use vidi_examples::{LoopHandler, build_egl_window, import_dmabuf_glutin};
#[derive(Debug, Clone)]
enum UserEvent {
NewStream(Arc<Mutex<StreamBorrowing>>),
}
fn main() -> io::Result<()> {
// Setup the GL display stuff
let event_loop = winit::event_loop::EventLoop::<UserEvent>::with_user_event().build()
.map_err(io::Error::other)?;
let event_loop_proxy = event_loop.create_proxy();
let (_window, display, egl_display) = build_egl_window(&event_loop);
let (backtx, backrx) = mpsc::channel();
let cameras_list = vidi::actors::camera_list::spawn()?;
let cameras = cameras_list.cameras();
// TODO: don't crash on zero cameras, show info instead
let camera = cameras_list.create(&cameras[0].info.id)
.unwrap().unwrap();
dbg!(camera.get_id());
// Allocate 4 buffers by default
let buffer_count = 4;
let camera = camera.acquire();
if let Ok(mut camera) = camera {
thread::spawn(move || {
let mut wrap = || -> Result<(), Box<dyn Error>> {
let stream = Arc::new(Mutex::new(
camera.start(
Config{fourcc: FourCC::new(b"YUYV"), width: 640, height: 480},
buffer_count,
)?
));
// this loop notifies the drawing loop of new waiting camera frames
loop {
{
let stream = stream.lock().unwrap();
stream.wait()?;
}
event_loop_proxy.send_event(UserEvent::NewStream(stream.clone()))?;
// Wait until the ui thread collects the frame.
// `stream.wait()` does not block when a frame is ready, so once it is, this loop would just keep spinning until the frame is collected.
let () = backrx.recv()?;
}
};
if let Err(e) = wrap() {
eprintln!("Camera thread exit: {:?}", e);
}
});
}
let shader = crispy::shaders::yuv::YuyvToRgba::new(
&display,
(640, 480),
).unwrap();
event_loop.run_app(&mut LoopHandler {
user_event: move |event| {
//let camera = camera.acquire().unwrap();
let t0 = Instant::now();
let t1 = Instant::now();
let stream = match event {
UserEvent::NewStream(stream) => stream,
};
// drawing a frame
let mut target = display.draw();
let mut stream = stream.lock().unwrap();
let buf;
loop {
match stream.next().unwrap() {
(b, meta, false) => {
buf = b;
println!(
"Buffer seq: {}, timestamp: {}",
meta.sequence,
meta.timestamp,
);
break;
},
(_b, meta, true) => {
println!(
"Buffer DROPPED seq: {}, timestamp: {}",
meta.sequence,
meta.timestamp,
);
},
}
}
let tex = import_dmabuf_glutin(
&display,
&egl_display,
buf.as_fd(),
// The buffer has two of R8 bytes per pixel.
(640*2, 480),
// This re-interprets the texture from YUYV to R8.
// GPUs don't seem to like YUYV textures, and crispy shaders aren't written with anything but R8 in mind.
// When in doubt, check shader documentation.
crispy::Format::R8,
).unwrap();
target.clear_color(0.0, 0.0, 0.0, 255.0);
shader.convert(
&display,
tex.get_texture(),
&mut target,
crispy::shaders::yuv::ColorSpace::BT709,
crispy::shaders::yuv::Gamma::Identity,
).unwrap();
target.finish().unwrap();
println!(
"ms: {}\t (buffer) + {}\t (UI)",
t1.duration_since(t0).as_millis(),
t1.elapsed().as_millis()
);
backtx.send(()).unwrap();
}
})
.map_err(io::Error::other)
}