├── .gitignore ├── src ├── lib.rs ├── error.rs ├── windows │ └── mod.rs └── linux │ └── mod.rs ├── .travis.yml ├── examples ├── hello_camera.rs ├── take_picture.rs ├── piston.rs └── glium.rs ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | 3 | #[cfg(unix)] 4 | pub use linux::*; 5 | #[cfg(windows)] 6 | pub use windows::*; 7 | 8 | pub mod error; 9 | 10 | #[cfg(unix)] 11 | pub mod linux; 12 | #[cfg(windows)] 13 | pub mod windows; 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | os: 7 | - windows 8 | - linux 9 | matrix: 10 | allow_failures: 11 | - rust: nightly 12 | addons: 13 | apt: 14 | packages: 15 | - libv4l-dev 16 | 17 | -------------------------------------------------------------------------------- /examples/hello_camera.rs: -------------------------------------------------------------------------------- 1 | extern crate camera_capture; 2 | 3 | fn main() { 4 | let cam = camera_capture::create(0).unwrap(); 5 | let cam = cam.fps(5.0).unwrap().start().unwrap(); 6 | for _image in cam { 7 | println!("frame"); 8 | } 9 | println!("done"); 10 | } 11 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | InvalidFps(Vec), 4 | InvalidResolution(Vec<(u32, u32)>), 5 | Io(std::io::Error), 6 | } 7 | 8 | impl From for Error { 9 | fn from(err: std::io::Error) -> Error { 10 | Error::Io(err) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/take_picture.rs: -------------------------------------------------------------------------------- 1 | extern crate camera_capture; 2 | extern crate image; 3 | 4 | use std::fs::File; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | let cam = camera_capture::create(0).unwrap(); 9 | 10 | let mut cam_iter = cam.fps(5.0).unwrap().start().unwrap(); 11 | let img = cam_iter.next().unwrap(); 12 | 13 | let file_name = "test.png"; 14 | let path = Path::new(&file_name); 15 | let _ = &mut File::create(&path).unwrap(); 16 | img.save(&path).unwrap(); 17 | 18 | println!("img saved to {}", file_name); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "camera_capture" 3 | version = "0.5.0" 4 | authors = ["Oliver Schneider "] 5 | license = "MIT" 6 | description = "capture webcam images on linux and windows" 7 | keywords = ["webcam", "v4l", "capture", "camera", "mf"] 8 | repository = "https://github.com/oli-obk/camera_capture" 9 | edition = "2018" 10 | 11 | build = "build.rs" 12 | 13 | [build-dependencies] 14 | pkg-config = "0.3.14" 15 | 16 | [dependencies] 17 | image = "0.21.1" 18 | 19 | [target.'cfg(windows)'.dependencies] 20 | escapi = "4" 21 | 22 | [target.'cfg(unix)'.dependencies] 23 | rscam = "0.5.4" 24 | 25 | [dev-dependencies] 26 | piston_window = "0.89.0" 27 | piston-texture = "0.6.0" 28 | glium = "0.24.0" 29 | failure = "0.1.5" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webcam capturing in Rust 2 | 3 | ## Description 4 | 5 | Captures webcam images and offers access to them through an iterator. Works with 6 | v4l2 on Linux. OSX is not supported. 7 | 8 | ## TODO 9 | 10 | * [ ] threaded access through channel `Receiver` 11 | * [ ] automatic webcam detection and selection 12 | 13 | ## Documentation 14 | You can create the documentation (locally) by executing `cargo doc --no-deps --open`. 15 | 16 | ## Example 17 | 18 | ```rust 19 | extern crate camera_capture; 20 | 21 | fn main() { 22 | let cam = camera_capture::create(0).unwrap(); 23 | let cam = cam.fps(5.0).unwrap().start().unwrap(); 24 | for _image in cam { 25 | println!("frame"); 26 | } 27 | println!("done"); 28 | } 29 | ``` 30 | 31 | ## Piston Example 32 | 33 | * run via `cargo run --example piston` 34 | * [source](https://github.com/oli-obk/camera_capture/blob/master/examples/piston.rs) 35 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | 3 | pub type Frame = Vec; 4 | 5 | pub struct ImageIterator { 6 | camera: escapi::Device, 7 | } 8 | 9 | pub struct Builder { 10 | resolution: (u32, u32), 11 | camera_id: u32, 12 | } 13 | 14 | pub fn create(i: u32) -> std::io::Result { 15 | Ok(Builder { 16 | camera_id: i, 17 | resolution: (640, 480), 18 | }) 19 | } 20 | 21 | impl Iterator for ImageIterator { 22 | type Item = image::ImageBuffer, Frame>; 23 | fn next(&mut self) -> Option { 24 | let wdt = self.camera.capture_width(); 25 | let hgt = self.camera.capture_height(); 26 | match self.camera.capture() { 27 | Ok(frame) => { 28 | let len = (wdt * hgt) as usize; 29 | let mut buf = vec![0; len * 3]; 30 | for i in 0..len { 31 | buf[i * 3 + 2] = frame[i * 4]; 32 | buf[i * 3 + 1] = frame[i * 4 + 1]; 33 | buf[i * 3] = frame[i * 4 + 2]; 34 | } 35 | image::ImageBuffer::from_raw(wdt, hgt, buf) 36 | } 37 | Err(_) => None, 38 | } 39 | } 40 | } 41 | 42 | impl Builder { 43 | pub fn fps(self, _fps: f64) -> Result { 44 | Ok(self) 45 | } 46 | 47 | pub fn resolution(mut self, wdt: u32, hgt: u32) -> Result { 48 | self.resolution = (wdt, hgt); 49 | Ok(self) 50 | } 51 | 52 | pub fn start(self) -> std::io::Result { 53 | match escapi::init(self.camera_id as usize, self.resolution.0, self.resolution.1, 10) { 54 | Ok(cam) => Ok(ImageIterator { 55 | camera: cam, 56 | }), 57 | Err(err) => Err(std::io::Error::new(std::io::ErrorKind::Other, err)), 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/piston.rs: -------------------------------------------------------------------------------- 1 | extern crate camera_capture; 2 | extern crate image; 3 | extern crate piston_window; 4 | 5 | use image::ConvertBuffer; 6 | use piston_window::{clear, PistonWindow, Texture, TextureSettings, WindowSettings}; 7 | 8 | fn main() { 9 | let mut window: PistonWindow = WindowSettings::new("piston: image", [300, 300]) 10 | .exit_on_esc(true) 11 | .build() 12 | .unwrap(); 13 | 14 | let mut tex: Option> = None; 15 | let (sender, receiver) = std::sync::mpsc::channel(); 16 | let imgthread = std::thread::spawn(move || { 17 | let res1 = camera_capture::create(0); 18 | if let Err(e) = res1 { 19 | eprintln!("could not open camera: {}", e); 20 | std::process::exit(1); 21 | } 22 | let res2 = res1.unwrap().fps(5.0).unwrap().start(); 23 | if let Err(e) = res2 { 24 | eprintln!("could retrieve data from camera: {}", e); 25 | std::process::exit(2); 26 | } 27 | let cam = res2.unwrap(); 28 | for frame in cam { 29 | if sender.send(frame.convert()).is_err() { 30 | break; 31 | } 32 | } 33 | }); 34 | 35 | while let Some(e) = window.next() { 36 | if let Ok(frame) = receiver.try_recv() { 37 | if let Some(mut t) = tex { 38 | t.update(&mut window.encoder, &frame).unwrap(); 39 | tex = Some(t); 40 | } else { 41 | tex = 42 | Texture::from_image(&mut window.factory, &frame, &TextureSettings::new()).ok(); 43 | } 44 | } 45 | window.draw_2d(&e, |c, g| { 46 | clear([1.0; 4], g); 47 | if let Some(ref t) = tex { 48 | piston_window::image(t, c.transform, g); 49 | } 50 | }); 51 | } 52 | drop(receiver); 53 | imgthread.join().unwrap(); 54 | } 55 | -------------------------------------------------------------------------------- /src/linux/mod.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | 3 | use crate::error::Error; 4 | 5 | pub type Frame = rscam::Frame; 6 | 7 | pub struct ImageIterator { 8 | camera: rscam::Camera, 9 | } 10 | 11 | pub struct Builder { 12 | fps: (u32, u32), 13 | resolution: (u32, u32), 14 | camera: rscam::Camera, 15 | } 16 | 17 | pub fn create(i: u32) -> std::io::Result { 18 | Ok(Builder { 19 | camera: rscam::Camera::new(&format!("/dev/video{}", i))?, 20 | resolution: (640, 480), 21 | fps: (1, 10), 22 | }) 23 | } 24 | 25 | impl Iterator for ImageIterator { 26 | type Item = image::ImageBuffer, Frame>; 27 | fn next(&mut self) -> Option { 28 | match self.camera.capture() { 29 | Ok(frame) => image::ImageBuffer::from_raw(frame.resolution.0, frame.resolution.1, frame), 30 | Err(_) => None, 31 | } 32 | } 33 | } 34 | 35 | impl Builder { 36 | pub fn fps(mut self, fps: f64) -> Result { 37 | if fps < 5.0 { 38 | self.fps = (1000, (fps * 1000.0) as u32); 39 | } else { 40 | self.fps = (1, fps as u32); 41 | } 42 | let intervals = match self.camera.intervals(b"RGB3", self.resolution) { 43 | Ok(intervals) => intervals, 44 | Err(rscam::Error::Io(io)) => return Err(Error::Io(io)), 45 | _ => unreachable!(), 46 | }; 47 | match intervals { 48 | rscam::IntervalInfo::Discretes(ref v) => { 49 | for &(a, b) in v { 50 | if a == self.fps.0 && b == self.fps.1 { 51 | return Ok(self); 52 | } 53 | } 54 | Err(Error::InvalidFps(v.iter().map(|&(a, b)| f64::from(a / b)).collect())) 55 | } 56 | rscam::IntervalInfo::Stepwise { min, max, step } => { 57 | if ((self.fps.0 - min.0) / step.0) * step.0 + min.0 == self.fps.0 58 | && ((self.fps.1 - min.1) / step.1) * step.1 + min.1 == self.fps.1 59 | && max.0 >= self.fps.0 60 | && max.1 >= self.fps.1 { 61 | Ok(self) 62 | } else { 63 | Err(Error::InvalidFps([min, max].iter().map(|&(a, b)| f64::from(a / b)).collect())) 64 | } 65 | } 66 | } 67 | } 68 | 69 | pub fn resolution(mut self, wdt: u32, hgt: u32) -> Result { 70 | self.resolution = (wdt, hgt); 71 | let res = match self.camera.resolutions(b"RGB3") { 72 | Ok(res) => res, 73 | Err(rscam::Error::Io(io)) => return Err(Error::Io(io)), 74 | _ => unreachable!(), 75 | }; 76 | match res { 77 | rscam::ResolutionInfo::Discretes(ref v) => { 78 | for &(w, h) in v { 79 | if w == wdt && h == hgt { 80 | return Ok(self); 81 | } 82 | } 83 | Err(Error::InvalidResolution(v.clone())) 84 | } 85 | rscam::ResolutionInfo::Stepwise { min, max, step } => { 86 | if ((wdt - min.0) / step.0) * step.0 + min.0 == wdt 87 | && ((hgt - min.1) / step.1) * step.1 + min.1 == hgt 88 | && max.0 >= wdt 89 | && max.1 >= hgt { 90 | Ok(self) 91 | } else { 92 | Err(Error::InvalidResolution(vec![min, max])) 93 | } 94 | } 95 | } 96 | } 97 | 98 | pub fn start(mut self) -> std::io::Result { 99 | match self.camera.start(&rscam::Config { 100 | interval: self.fps, // 30 fps. 101 | resolution: self.resolution, 102 | format: b"RGB3", 103 | ..Default::default() 104 | }) { 105 | Ok(()) => Ok(ImageIterator { camera: self.camera }), 106 | Err(rscam::Error::Io(io)) => Err(io), 107 | _ => unreachable!(), 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/glium.rs: -------------------------------------------------------------------------------- 1 | extern crate camera_capture; 2 | #[macro_use] extern crate failure; 3 | #[macro_use] extern crate glium; 4 | extern crate image; 5 | 6 | 7 | use std::time::Duration; 8 | use std::sync::mpsc::{self, Receiver}; 9 | use std::thread::{self, JoinHandle}; 10 | 11 | use failure::{Error, SyncFailure}; 12 | use glium::texture::{CompressedSrgbTexture2d, RawImage2d}; 13 | use glium::backend::Facade; 14 | use glium::{glutin, Display, IndexBuffer, Program, Surface, VertexBuffer}; 15 | use glium::glutin::{EventsLoop, WindowBuilder, ContextBuilder}; 16 | use image::{ImageBuffer, Rgb}; 17 | 18 | 19 | fn main() { 20 | if let Err(e) = run() { 21 | eprintln!("An error occured: {}", e); 22 | for cause in e.iter_chain().skip(1) { 23 | eprintln!("... caused by: {}", cause); 24 | } 25 | } 26 | } 27 | 28 | fn run() -> Result<(), Error> { 29 | // Building the display. This object is the main object, containing the 30 | // OpenGL facade and everything else. 31 | let (mut events_loop, display) = create_display()?; 32 | 33 | // This will start a new thread which pulls images from the webcam and 34 | // sends them into a channel. We get the receiving end of the channel, 35 | // called `webcam_frames`. 36 | let (webcam_frames, webcam_thread) = start_webcam()?; 37 | 38 | // Build buffers for a fullscreen quad. 39 | let (vertex_buffer, index_buffer) = quad_buffers(&display)?; 40 | 41 | // Create a simple program to draw an image to the fullscreen quad 42 | let program = create_program(&display)?; 43 | 44 | let mut stop = false; 45 | while !stop { 46 | // We only wait for a new webcam image until some timeout is reached. 47 | let timeout = Duration::from_millis(500); 48 | if let Ok(frame) = webcam_frames.recv_timeout(timeout) { 49 | let (frame_width, frame_height) = frame.dimensions(); 50 | 51 | // Take the frame from the webcam and convert it to a texture. 52 | let image = RawImage2d::from_raw_rgb_reversed( 53 | &frame, 54 | (frame_width, frame_height), 55 | ); 56 | let texture = CompressedSrgbTexture2d::new(&display, image)?; 57 | 58 | // Pass the texture to the shader via uniforms. 59 | let uniforms = uniform! { tex: &texture }; 60 | 61 | // Finally, draw the image on the screen 62 | let mut target = display.draw(); 63 | target.clear_color(0.0, 0.0, 0.0, 0.0); 64 | target.draw( 65 | &vertex_buffer, 66 | &index_buffer, 67 | &program, 68 | &uniforms, 69 | &Default::default() 70 | )?; 71 | target.finish()?; 72 | 73 | // Polling and handling the events received by the window. 74 | events_loop.poll_events(|event| { 75 | use glutin::{ElementState, VirtualKeyCode}; 76 | 77 | // We are only interested in window events. 78 | let event = match event { 79 | glutin::Event::WindowEvent { event, .. } => event, 80 | _ => return, 81 | }; 82 | 83 | match event { 84 | // Stop the application when the close-button is clicked or 85 | // ESC is pressed 86 | glutin::WindowEvent::CloseRequested | 87 | glutin::WindowEvent::KeyboardInput { 88 | input: glutin::KeyboardInput { 89 | state: ElementState::Pressed, 90 | virtual_keycode: Some(VirtualKeyCode::Escape), 91 | .. 92 | }, 93 | .. 94 | } => stop = true, 95 | _ => {} 96 | } 97 | }); 98 | } else { 99 | bail!("Webcam thread was killed or did not responded for {:?}. Stopping.", timeout); 100 | } 101 | } 102 | 103 | // After stopping the main program, we want to gracefully stop the thread 104 | // pulling in the webcam images. 105 | stop_webcam(webcam_frames, webcam_thread); 106 | 107 | Ok(()) 108 | } 109 | 110 | 111 | // =========================================================================== 112 | // ===== Webcam helper functions 113 | // =========================================================================== 114 | pub type CamFrame = ImageBuffer, camera_capture::Frame>; 115 | 116 | pub fn start_webcam() -> Result<(Receiver, JoinHandle<()>), Error> { 117 | let (sender, receiver) = mpsc::channel(); 118 | let cam = camera_capture::create(0).unwrap() 119 | .fps(30.0) 120 | .unwrap() 121 | .start()?; 122 | 123 | let webcam_thread = thread::spawn(move || { 124 | for frame in cam { 125 | if sender.send(frame).is_err() { 126 | break; 127 | } 128 | } 129 | }); 130 | 131 | Ok((receiver, webcam_thread)) 132 | } 133 | 134 | pub fn stop_webcam(receiver: Receiver, thread: JoinHandle<()>) { 135 | // We close our channel which will cause the other thread to stop itself. 136 | // The main thread then just waits for this to happen. 137 | drop(receiver); 138 | if thread.join().is_err() { 139 | eprintln!("The webcam thread panicked before we tried to join it..."); 140 | } 141 | } 142 | 143 | 144 | // =========================================================================== 145 | // ===== OpenGL helper functions 146 | // =========================================================================== 147 | 148 | /// Creates the OpenGL context. 149 | pub fn create_display() -> Result<(EventsLoop, Display), Error> { 150 | // Create the event loop for the window 151 | let events_loop = EventsLoop::new(); 152 | 153 | // Configure the window 154 | let window = WindowBuilder::new() 155 | .with_title("camera_capture example"); ; 156 | 157 | // Configure the OpenGL context 158 | let context = ContextBuilder::new(); 159 | 160 | // Put all together and create a finished "display" 161 | glium::Display::new(window, context, &events_loop) 162 | .map_err(|e| SyncFailure::new(e).into()) 163 | .map(|context| (events_loop, context)) 164 | } 165 | 166 | #[derive(Copy, Clone)] 167 | pub struct Vertex { 168 | position: [f32; 2], 169 | tex_coords: [f32; 2], 170 | } 171 | 172 | implement_vertex!(Vertex, position, tex_coords); 173 | 174 | /// Creates and returns a vertex-/index-buffer pair which represents a full 175 | /// screen quad (we don't want to do any fancy 3D drawing, just simply draw 176 | /// a texture on the whole screen, that is: the part inside of our window). 177 | pub fn quad_buffers( 178 | display: &F 179 | ) -> Result<(VertexBuffer, IndexBuffer), Error> { 180 | use glium::index::PrimitiveType; 181 | 182 | // The vertex buffer simply contains the OpenGL screen coordinates. That 183 | // way we can simply draw on the full screen. 184 | let vertex_data = [ 185 | Vertex { position: [-1.0, -1.0], tex_coords: [0.0, 0.0] }, 186 | Vertex { position: [-1.0, 1.0], tex_coords: [0.0, 1.0] }, 187 | Vertex { position: [ 1.0, 1.0], tex_coords: [1.0, 1.0] }, 188 | Vertex { position: [ 1.0, -1.0], tex_coords: [1.0, 0.0] }, 189 | ]; 190 | 191 | // Create both OpenGL buffers 192 | let vertex_buffer = VertexBuffer::new( 193 | display, 194 | &vertex_data, 195 | )?; 196 | let index_buffer = IndexBuffer::new( 197 | display, 198 | PrimitiveType::TriangleStrip, 199 | &[1 as u16, 2, 0, 3], 200 | )?; 201 | 202 | Ok((vertex_buffer, index_buffer)) 203 | } 204 | 205 | /// Creates a simple shader program which simply renders a texture `tex` with 206 | /// the texture coordinates `tex_coords`. 207 | pub fn create_program(display: &F) -> Result { 208 | // Compiling shaders and linking them together 209 | program!(display, 210 | 140 => { 211 | vertex: " 212 | #version 140 213 | 214 | in vec2 position; 215 | in vec2 tex_coords; 216 | 217 | out vec2 v_tex_coords; 218 | 219 | void main() { 220 | gl_Position = vec4(position, 0.0, 1.0); 221 | v_tex_coords = tex_coords; 222 | } 223 | ", 224 | 225 | fragment: " 226 | #version 140 227 | uniform sampler2D tex; 228 | in vec2 v_tex_coords; 229 | out vec4 f_color; 230 | 231 | void main() { 232 | f_color = texture(tex, v_tex_coords); 233 | } 234 | " 235 | }, 236 | ).map_err(std::convert::Into::into) 237 | } 238 | --------------------------------------------------------------------------------