├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── mirror.rs ├── src ├── context.rs ├── controls.rs ├── device.rs ├── error.rs ├── formats.rs ├── frame.rs ├── lib.rs └── streaming.rs ├── uvc-src ├── Cargo.toml ├── build.rs └── src │ └── lib.rs └── uvc-sys ├── Cargo.toml ├── build.rs ├── src └── lib.rs └── wrapper.h /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '0 0 15 * *' 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | RUSTFLAGS: -D warnings 14 | 15 | jobs: 16 | rustfmt: 17 | name: rustfmt 18 | runs-on: ubuntu-18.04 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v2 22 | with: {submodules: true} 23 | - name: Install rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | override: true 28 | profile: minimal 29 | components: rustfmt 30 | - name: Check formatting 31 | run: cargo fmt -- --check 32 | 33 | documentation: 34 | name: documentation 35 | runs-on: ubuntu-18.04 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | with: {submodules: true} 40 | - name: Install rust 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | override: true 45 | profile: minimal 46 | - name: Documentation 47 | run: cargo doc --all-features --workspace 48 | 49 | clippy: 50 | name: clippy 51 | runs-on: ubuntu-18.04 52 | steps: 53 | - name: Checkout repository 54 | uses: actions/checkout@v2 55 | with: {submodules: true} 56 | - name: Install rust 57 | uses: actions-rs/toolchain@v1 58 | with: 59 | toolchain: stable 60 | override: true 61 | profile: minimal 62 | components: clippy 63 | - name: Run Clippy 64 | run: cargo clippy --workspace --all-features 65 | 66 | all_vendored_platforms: 67 | name: test 68 | runs-on: ${{ matrix.os }} 69 | strategy: 70 | matrix: 71 | include: 72 | - build: ubuntu 73 | os: ubuntu-latest 74 | rust: stable 75 | - build: macos 76 | os: macos-latest 77 | rust: stable 78 | # - build: win-msvc 79 | # os: windows-2019 80 | # rust: stable-msvc 81 | # - build: win-gnu 82 | # os: windows-2019 83 | # rust: stable-gnu 84 | steps: 85 | - name: Checkout repository 86 | uses: actions/checkout@v2 87 | with: {submodules: true} 88 | 89 | - name: Install rust 90 | uses: actions-rs/toolchain@v1 91 | with: 92 | toolchain: ${{ matrix.rust }} 93 | profile: minimal 94 | override: true 95 | 96 | - name: Build 97 | run: cargo build --features vendor 98 | 99 | - name: Build example 100 | run: cargo build --example mirror --features vendor 101 | 102 | system_uvc: 103 | name: Using system libraries 104 | runs-on: ubuntu-latest 105 | steps: 106 | - name: Install libuvc 107 | run: sudo apt-get install libuvc-dev 108 | 109 | - name: Checkout repository 110 | uses: actions/checkout@v2 111 | with: {submodules: false} 112 | 113 | - name: Install rust 114 | uses: actions-rs/toolchain@v1 115 | with: 116 | toolchain: nightly 117 | override: true 118 | profile: minimal 119 | 120 | - name: Build 121 | run: cargo build 122 | 123 | - name: Build example 124 | run: cargo build --example mirror 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /uvc-sys/target 4 | /cammy/target 5 | /Cargo.lock 6 | /uvc-sys/Cargo.lock 7 | /uvc-src/Cargo.lock 8 | /uvc-src/target 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "uvc-src/source"] 2 | path = uvc-src/source 3 | url = https://github.com/libuvc/libuvc.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uvc" 3 | description = "Safe and ergonomic wrapper around libuvc, allowing capture of webcam streams" 4 | version = "0.3.0" 5 | authors = ["Magnus Ulimoen "] 6 | license = "MIT" 7 | repository = "https://github.com/mulimoen/libuvc-rs.git" 8 | categories = ["api-bindings", "multimedia::video"] 9 | keywords = ["webcam", "capture", "camera"] 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | uvc-sys = { path = "uvc-sys", version = "0.3.0" } 15 | 16 | [dev-dependencies] 17 | glium = "0.35.0" 18 | 19 | [features] 20 | vendor = ["uvc-sys/vendor"] 21 | uvc_debugging = ["uvc-sys/uvc_debugging"] 22 | 23 | [workspace] 24 | members = [ 25 | "uvc-src", 26 | "uvc-sys", 27 | ] 28 | 29 | [package.metadata.docs.rs] 30 | no-default-features = true 31 | features = ["vendor"] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Magnus Ulimoen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Safe rust wrapper around [libuvc](https://int80k.com/libuvc/doc/) 2 | 3 | [![crates.io](https://img.shields.io/crates/v/uvc.svg)](https://crates.io/crates/uvc) 4 | [![license](https://img.shields.io/crates/l/uvc.svg)](https://github.com/mulimoen/libuvc-rs/blob/master/LICENSE) 5 | 6 | ## What does this library do? 7 | 8 | 9 | This library gives access to the webcam, and allows one to capture the video stream. An example of how to use this library can be found in the examples directory. 10 | 11 | An error such as `Access` might be due to the program not having read/write access to the usb device. You can grant access to all users by changing the device permissions, for example with 12 | 13 | ``` 14 | chmod 0666 /dev/bus/usb/{BUS}/{DEVICE} 15 | ``` 16 | 17 | where BUS and DEVICE can be found with `lsusb` or by running the `mirror` example. 18 | 19 | ## Documentation 20 | Documentation can be created with `cargo doc` 21 | 22 | ## Dependencies 23 | To use this crate, the `libuvc` native dependency must be installed, or vendored using the `vendor` feature. Disable the default-features and choose the feature `vendor` or `system` to select supplier. 24 | -------------------------------------------------------------------------------- /examples/mirror.rs: -------------------------------------------------------------------------------- 1 | use glium::{implement_vertex, uniform}; 2 | 3 | use std::error::Error; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | use glium::Surface; 7 | use uvc::{Context, Frame}; 8 | 9 | fn frame_to_raw_image( 10 | frame: &Frame, 11 | ) -> Result, Box> { 12 | let new_frame = frame.to_rgb()?; 13 | let data = new_frame.to_bytes(); 14 | 15 | let image = glium::texture::RawImage2d::from_raw_rgb( 16 | data.to_vec(), 17 | (new_frame.width(), new_frame.height()), 18 | ); 19 | 20 | Ok(image) 21 | } 22 | 23 | fn callback_frame_to_image( 24 | frame: &Frame, 25 | data: &mut Arc>>>, 26 | ) { 27 | let image = frame_to_raw_image(frame); 28 | match image { 29 | Err(x) => println!("{:#?}", x), 30 | Ok(x) => { 31 | let mut data = Mutex::lock(&data).unwrap(); 32 | *data = Some(x); 33 | } 34 | } 35 | } 36 | 37 | fn main() { 38 | let ctx = Context::new().expect("Could not create context"); 39 | let dev = ctx 40 | .find_device(None, None, None) 41 | .expect("Could not find device"); 42 | 43 | let description = dev.description().unwrap(); 44 | println!( 45 | "Found device: Bus {:03} Device {:03} : ID {:04x}:{:04x} {} ({})", 46 | dev.bus_number(), 47 | dev.device_address(), 48 | description.vendor_id, 49 | description.product_id, 50 | description.product.unwrap_or_else(|| "Unknown".to_owned()), 51 | description 52 | .manufacturer 53 | .unwrap_or_else(|| "Unknown".to_owned()) 54 | ); 55 | 56 | // Open multiple devices by enumerating: 57 | // let mut list = ctx.devices().expect("Could not get devices"); 58 | // let dev = list.next().expect("No device available"); 59 | 60 | let devh = dev.open().expect("Could not open device"); 61 | 62 | let format = devh 63 | .get_preferred_format(|x, y| { 64 | if x.fps >= y.fps && x.width * x.height >= y.width * y.height { 65 | x 66 | } else { 67 | y 68 | } 69 | }) 70 | .unwrap(); 71 | 72 | println!("Best format found: {:?}", format); 73 | let mut streamh = devh.get_stream_handle_with_format(format).unwrap(); 74 | 75 | println!( 76 | "Scanning mode: {:?}\nAuto-exposure mode: {:?}\nAuto-exposure priority: {:?}\nAbsolute exposure: {:?}\nRelative exposure: {:?}\nAboslute focus: {:?}\nRelative focus: {:?}", 77 | devh.scanning_mode(), 78 | devh.ae_mode(), 79 | devh.ae_priority(), 80 | devh.exposure_abs(), 81 | devh.exposure_rel(), 82 | devh.focus_abs(), 83 | devh.focus_rel(), 84 | ); 85 | 86 | let frame = Arc::new(Mutex::new(None)); 87 | let _stream = streamh 88 | .start_stream(callback_frame_to_image, frame.clone()) 89 | .unwrap(); 90 | 91 | use glium::glutin; 92 | let events_loop = glutin::event_loop::EventLoop::new(); 93 | let window = glutin::window::WindowBuilder::new().with_title("Mirror"); 94 | let context = glutin::ContextBuilder::new(); 95 | let display = glium::Display::new(window, context, &events_loop).unwrap(); 96 | 97 | #[derive(Copy, Clone)] 98 | pub struct QuadVertex { 99 | pos: (f32, f32), 100 | } 101 | 102 | implement_vertex!(QuadVertex, pos); 103 | 104 | let vertices: [QuadVertex; 4] = [ 105 | QuadVertex { pos: (-1.0, -1.0) }, 106 | QuadVertex { pos: (-1.0, 1.0) }, 107 | QuadVertex { pos: (1.0, -1.0) }, 108 | QuadVertex { pos: (1.0, 1.0) }, 109 | ]; 110 | 111 | let indices: [u8; 6] = [0, 1, 2, 1, 3, 2]; 112 | 113 | let vertex_shader_source = r#" 114 | #version 140 115 | 116 | in vec2 pos; 117 | 118 | out vec2 v_position; 119 | 120 | void main() { 121 | v_position = (pos + 1.0)/2.0; 122 | gl_Position = vec4(-pos.x, -pos.y, 0.0, 1.0); 123 | } 124 | "#; 125 | 126 | let fragment_shader_source = r#" 127 | #version 140 128 | 129 | in vec2 v_position; 130 | 131 | out vec4 colour; 132 | 133 | uniform sampler2D u_image; 134 | 135 | void main() { 136 | vec2 pos = v_position; 137 | 138 | colour = texture(u_image, pos); 139 | } 140 | "#; 141 | 142 | let vertices = glium::VertexBuffer::new(&display, &vertices).unwrap(); 143 | let indices = glium::IndexBuffer::new( 144 | &display, 145 | glium::index::PrimitiveType::TrianglesList, 146 | &indices, 147 | ) 148 | .unwrap(); 149 | let program = 150 | glium::Program::from_source(&display, vertex_shader_source, fragment_shader_source, None) 151 | .unwrap(); 152 | 153 | let mut buffer: Option = None; 154 | events_loop.run(move |event, _, control_flow| { 155 | if let glutin::event::Event::WindowEvent { event, .. } = event { 156 | if let glutin::event::WindowEvent::CloseRequested = event { 157 | *control_flow = glutin::event_loop::ControlFlow::Exit; 158 | return; 159 | } 160 | } 161 | 162 | let mut target = display.draw(); 163 | target.clear_color(0.0, 0.0, 1.0, 1.0); 164 | 165 | let mut mutex = Mutex::lock(&frame).unwrap(); 166 | 167 | match mutex.take() { 168 | None => { 169 | // No new frame to render 170 | } 171 | Some(image) => { 172 | let image = glium::texture::SrgbTexture2d::new(&display, image) 173 | .expect("Could not use image"); 174 | buffer = Some(image); 175 | } 176 | } 177 | 178 | if let Some(ref b) = buffer { 179 | let uniforms = uniform! { u_image: b }; 180 | target 181 | .draw( 182 | &vertices, 183 | &indices, 184 | &program, 185 | &uniforms, 186 | &Default::default(), 187 | ) 188 | .unwrap(); 189 | } 190 | 191 | target.finish().unwrap(); 192 | }); 193 | } 194 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use uvc_sys::*; 2 | 3 | use crate::device::{Device, DeviceList}; 4 | use crate::error::{Error, Result}; 5 | 6 | use std::ffi::CString; 7 | use std::marker::PhantomData; 8 | use std::os::raw::c_int; 9 | use std::ptr::NonNull; 10 | 11 | unsafe impl<'a> Send for Context<'a> {} 12 | unsafe impl<'a> Sync for Context<'a> {} 13 | #[derive(Debug)] 14 | /// Contains the `libuvc` context 15 | pub struct Context<'a> { 16 | ctx: NonNull, 17 | _ctx: PhantomData<&'a uvc_context>, 18 | } 19 | 20 | impl<'a> Drop for Context<'a> { 21 | fn drop(&mut self) { 22 | unsafe { 23 | uvc_exit(self.ctx.as_ptr()); 24 | } 25 | } 26 | } 27 | 28 | impl<'a> Context<'a> { 29 | /// Creates a new context 30 | pub fn new() -> Result { 31 | unsafe { 32 | let mut ctx = std::mem::MaybeUninit::<*mut uvc_context>::uninit(); 33 | let err = uvc_init(ctx.as_mut_ptr(), std::ptr::null_mut()).into(); 34 | if err == Error::Success { 35 | Ok(Context { 36 | ctx: NonNull::new(ctx.assume_init()).unwrap(), 37 | _ctx: PhantomData, 38 | }) 39 | } else { 40 | Err(err) 41 | } 42 | } 43 | } 44 | 45 | /// Enumerates the available devices 46 | pub fn devices(&'a self) -> Result> { 47 | unsafe { 48 | let mut list = std::mem::MaybeUninit::<*mut *mut uvc_device>::uninit(); 49 | let err = uvc_get_device_list(self.ctx.as_ptr(), list.as_mut_ptr()).into(); 50 | if err != Error::Success { 51 | return Err(err); 52 | } 53 | 54 | Ok(DeviceList::new(NonNull::new(list.assume_init()).unwrap())) 55 | } 56 | } 57 | 58 | /// Find a device based on informations about the device 59 | /// Pass None to all fields to get a default device 60 | pub fn find_device( 61 | &'a self, 62 | vendor_id: Option, 63 | product_id: Option, 64 | serial_number: Option<&str>, 65 | ) -> Result> { 66 | unsafe { 67 | let mut device = std::mem::MaybeUninit::<*mut uvc_device>::uninit(); 68 | let cstr = serial_number.map(|v| CString::new(v).unwrap()); 69 | let err = uvc_find_device( 70 | self.ctx.as_ptr(), 71 | device.as_mut_ptr(), 72 | vendor_id.unwrap_or(0), 73 | product_id.unwrap_or(0), 74 | cstr.map_or(std::ptr::null(), |v| v.as_ptr()), 75 | ) 76 | .into(); 77 | if err != Error::Success { 78 | return Err(err); 79 | } 80 | Ok(Device::from_raw(device.assume_init())) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/controls.rs: -------------------------------------------------------------------------------- 1 | use crate::device::DeviceHandle; 2 | use crate::error::{Error, Result}; 3 | use uvc_sys::*; 4 | 5 | #[derive(Copy, Clone, Debug)] 6 | pub enum ScanningMode { 7 | Interlaced, 8 | Progressive, 9 | } 10 | 11 | #[derive(Copy, Clone, Debug)] 12 | pub enum AutoExposureMode { 13 | Manual, 14 | Auto, 15 | ShutterPriority, 16 | AperturePriority, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug)] 20 | pub enum AutoExposurePriority { 21 | Constant, 22 | Variable, 23 | } 24 | 25 | impl<'a> DeviceHandle<'a> { 26 | pub fn scanning_mode(&self) -> Result { 27 | unsafe { 28 | let mut mode = std::mem::MaybeUninit::uninit(); 29 | let err = uvc_get_scanning_mode( 30 | self.devh.as_ptr(), 31 | mode.as_mut_ptr(), 32 | uvc_req_code_UVC_GET_CUR, 33 | ) 34 | .into(); 35 | if err != Error::Success { 36 | return Err(err); 37 | } 38 | match mode.assume_init() { 39 | 0 => Ok(ScanningMode::Interlaced), 40 | 1 => Ok(ScanningMode::Progressive), 41 | _ => Err(Error::Other), 42 | } 43 | } 44 | } 45 | pub fn ae_mode(&self) -> Result { 46 | unsafe { 47 | let mut mode = std::mem::MaybeUninit::uninit(); 48 | let err = uvc_get_ae_mode( 49 | self.devh.as_ptr(), 50 | mode.as_mut_ptr(), 51 | uvc_req_code_UVC_GET_CUR, 52 | ) 53 | .into(); 54 | if err != Error::Success { 55 | return Err(err); 56 | } 57 | match mode.assume_init() { 58 | 1 => Ok(AutoExposureMode::Manual), 59 | 2 => Ok(AutoExposureMode::Auto), 60 | 4 => Ok(AutoExposureMode::ShutterPriority), 61 | 8 => Ok(AutoExposureMode::AperturePriority), 62 | _ => Err(Error::Other), 63 | } 64 | } 65 | } 66 | pub fn ae_priority(&self) -> Result { 67 | unsafe { 68 | let mut priority = std::mem::MaybeUninit::uninit(); 69 | let err = uvc_get_ae_priority( 70 | self.devh.as_ptr(), 71 | priority.as_mut_ptr(), 72 | uvc_req_code_UVC_GET_CUR, 73 | ) 74 | .into(); 75 | if err != Error::Success { 76 | return Err(err); 77 | } 78 | match priority.assume_init() { 79 | 0 => Ok(AutoExposurePriority::Constant), 80 | 1 => Ok(AutoExposurePriority::Variable), 81 | _ => Err(Error::Other), 82 | } 83 | } 84 | } 85 | pub fn exposure_abs(&self) -> Result { 86 | unsafe { 87 | let mut time = std::mem::MaybeUninit::uninit(); 88 | let err = uvc_get_exposure_abs( 89 | self.devh.as_ptr(), 90 | time.as_mut_ptr(), 91 | uvc_req_code_UVC_GET_CUR, 92 | ) 93 | .into(); 94 | if err == Error::Success { 95 | Ok(time.assume_init()) 96 | } else { 97 | Err(err) 98 | } 99 | } 100 | } 101 | pub fn exposure_rel(&self) -> Result { 102 | unsafe { 103 | let mut step = std::mem::MaybeUninit::uninit(); 104 | let err = uvc_get_exposure_rel( 105 | self.devh.as_ptr(), 106 | step.as_mut_ptr(), 107 | uvc_req_code_UVC_GET_CUR, 108 | ) 109 | .into(); 110 | if err == Error::Success { 111 | Ok(step.assume_init()) 112 | } else { 113 | Err(err) 114 | } 115 | } 116 | } 117 | pub fn focus_abs(&self) -> Result { 118 | unsafe { 119 | let mut focus = std::mem::MaybeUninit::uninit(); 120 | let err = uvc_get_focus_abs( 121 | self.devh.as_ptr(), 122 | focus.as_mut_ptr(), 123 | uvc_req_code_UVC_GET_CUR, 124 | ) 125 | .into(); 126 | if err == Error::Success { 127 | Ok(focus.assume_init()) 128 | } else { 129 | Err(err) 130 | } 131 | } 132 | } 133 | pub fn focus_rel(&self) -> Result<(i8, u8)> { 134 | unsafe { 135 | let mut focus_rel = std::mem::MaybeUninit::uninit(); 136 | let mut speed = std::mem::MaybeUninit::uninit(); 137 | let err = uvc_get_focus_rel( 138 | self.devh.as_ptr(), 139 | focus_rel.as_mut_ptr(), 140 | speed.as_mut_ptr(), 141 | uvc_req_code_UVC_GET_CUR, 142 | ) 143 | .into(); 144 | if err == Error::Success { 145 | Ok((focus_rel.assume_init(), speed.assume_init())) 146 | } else { 147 | Err(err) 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::marker::PhantomData; 3 | use std::ptr::NonNull; 4 | use std::slice; 5 | use std::time::Duration; 6 | 7 | use crate::error::{Error, Result}; 8 | use crate::formats::{FrameFormat, StreamFormat}; 9 | use crate::streaming::StreamHandle; 10 | use uvc_sys::*; 11 | 12 | unsafe impl<'a> Send for DeviceList<'a> {} 13 | unsafe impl<'a> Sync for DeviceList<'a> {} 14 | #[derive(Debug)] 15 | /// List of camera devices, iterate to get the device(s) 16 | pub struct DeviceList<'a> { 17 | start: *mut *mut uvc_device, 18 | list: NonNull<*mut uvc_device>, 19 | _ph: PhantomData<&'a &'a uvc_device>, 20 | } 21 | 22 | impl<'a> Drop for DeviceList<'a> { 23 | fn drop(&mut self) { 24 | unsafe { uvc_free_device_list(self.start, false as u8) } 25 | } 26 | } 27 | 28 | impl<'a> DeviceList<'a> { 29 | pub(crate) fn new(list: NonNull<*mut uvc_device>) -> Self { 30 | Self { 31 | start: list.as_ptr(), 32 | list, 33 | _ph: PhantomData, 34 | } 35 | } 36 | } 37 | 38 | impl<'a> Iterator for DeviceList<'a> { 39 | type Item = Device<'a>; 40 | 41 | fn next(&mut self) -> Option> { 42 | let item = self.list.as_ptr(); 43 | if unsafe { (*item).is_null() } { 44 | return None; 45 | } 46 | 47 | let device = unsafe { Device::from_raw(*item) }; 48 | self.list = unsafe { NonNull::new(self.list.as_ptr().add(1)).unwrap() }; 49 | 50 | Some(device) 51 | } 52 | } 53 | 54 | unsafe impl<'a> Send for Device<'a> {} 55 | unsafe impl<'a> Sync for Device<'a> {} 56 | #[derive(Debug)] 57 | /// Device that can be opened 58 | pub struct Device<'a> { 59 | dev: NonNull, 60 | _dev: PhantomData<&'a uvc_device>, 61 | } 62 | 63 | impl<'a> Drop for Device<'a> { 64 | fn drop(&mut self) { 65 | unsafe { uvc_unref_device(self.dev.as_ptr()) }; 66 | } 67 | } 68 | 69 | impl<'a> Device<'a> { 70 | pub(crate) unsafe fn from_raw(dev: *mut uvc_device) -> Self { 71 | Device { 72 | dev: NonNull::new(dev).unwrap(), 73 | _dev: PhantomData, 74 | } 75 | } 76 | /// Create handle to a device 77 | pub fn open(&'a self) -> Result> { 78 | unsafe { 79 | let mut devh = std::mem::MaybeUninit::uninit(); 80 | let err = uvc_open(self.dev.as_ptr(), devh.as_mut_ptr()).into(); 81 | match err { 82 | Error::Success => Ok(DeviceHandle { 83 | devh: NonNull::new(devh.assume_init()).unwrap(), 84 | _devh: PhantomData, 85 | }), 86 | err => Err(err), 87 | } 88 | } 89 | } 90 | /// Get the description of a device 91 | pub fn description(&self) -> Result { 92 | unsafe { 93 | let mut desc = std::mem::MaybeUninit::uninit(); 94 | let err = uvc_get_device_descriptor(self.dev.as_ptr(), desc.as_mut_ptr()).into(); 95 | if err != Error::Success { 96 | return Err(err); 97 | } 98 | 99 | let desc = desc.assume_init(); 100 | 101 | let vendor_id = (*desc).idVendor; 102 | let product_id = (*desc).idProduct; 103 | let bcd_uvc = (*desc).bcdUVC; 104 | 105 | let serial_number_c_str = (*desc).serialNumber; 106 | let serial_number = if serial_number_c_str.is_null() { 107 | None 108 | } else { 109 | Some( 110 | CStr::from_ptr(serial_number_c_str) 111 | .to_owned() 112 | .into_string() 113 | .unwrap(), 114 | ) 115 | }; 116 | let manufacturer_c_str = (*desc).manufacturer; 117 | let manufacturer = if manufacturer_c_str.is_null() { 118 | None 119 | } else { 120 | Some( 121 | CStr::from_ptr(manufacturer_c_str) 122 | .to_owned() 123 | .into_string() 124 | .unwrap(), 125 | ) 126 | }; 127 | let product_c_str = (*desc).product; 128 | let product = if product_c_str.is_null() { 129 | None 130 | } else { 131 | Some( 132 | CStr::from_ptr(product_c_str) 133 | .to_owned() 134 | .into_string() 135 | .unwrap(), 136 | ) 137 | }; 138 | let descp = Ok(DeviceDescription { 139 | vendor_id, 140 | product_id, 141 | bcd_uvc, 142 | serial_number, 143 | manufacturer, 144 | product, 145 | }); 146 | 147 | uvc_free_device_descriptor(desc); 148 | 149 | descp 150 | } 151 | } 152 | 153 | /// Bus number of which this device is connected 154 | #[must_use] 155 | pub fn bus_number(&self) -> u8 { 156 | unsafe { uvc_get_bus_number(self.dev.as_ptr()) } 157 | } 158 | 159 | /// Device address within the bus 160 | #[must_use] 161 | pub fn device_address(&self) -> u8 { 162 | unsafe { uvc_get_device_address(self.dev.as_ptr()) } 163 | } 164 | } 165 | 166 | unsafe impl<'a> Send for DeviceHandle<'a> {} 167 | unsafe impl<'a> Sync for DeviceHandle<'a> {} 168 | #[derive(Debug)] 169 | /// Open handle to a device 170 | pub struct DeviceHandle<'a> { 171 | pub(crate) devh: NonNull, 172 | _devh: PhantomData<&'a uvc_device_handle>, 173 | } 174 | 175 | impl<'a, 'b> DeviceHandle<'a> { 176 | /// List all supported formats 177 | #[must_use] 178 | pub fn supported_formats(&self) -> FormatDescriptors<'a> { 179 | unsafe { 180 | let format_descs = uvc_get_format_descs(self.devh.as_ptr()); 181 | 182 | FormatDescriptors { 183 | head: format_descs, 184 | _ph: PhantomData, 185 | } 186 | } 187 | } 188 | 189 | /// Iterates over all available formats to select the best format. 190 | /// 191 | /// f should compare (x, y) and return the preferred format. 192 | pub fn get_preferred_format(&self, f: F) -> Option 193 | where 194 | F: Fn(StreamFormat, StreamFormat) -> StreamFormat, 195 | { 196 | let mut pref_format = None; 197 | for i in self.supported_formats() { 198 | for j in i.supported_formats() { 199 | for k in j.intervals() { 200 | let format = StreamFormat { 201 | width: u32::from(j.width()), 202 | height: u32::from(j.height()), 203 | fps: 10_000_000 / *k, 204 | format: match j.subtype() { 205 | DescriptionSubtype::FormatMJPEG | DescriptionSubtype::FrameMJPEG => { 206 | FrameFormat::MJPEG 207 | } 208 | DescriptionSubtype::FormatUncompressed 209 | | DescriptionSubtype::FrameUncompressed => FrameFormat::Uncompressed, 210 | _ => FrameFormat::Any, 211 | }, 212 | }; 213 | pref_format = Some(pref_format.map_or(format, |x| f(x, format))); 214 | } 215 | } 216 | } 217 | pref_format 218 | } 219 | 220 | /// Creates a stream handle 221 | pub fn get_stream_handle_with_format_size_and_fps( 222 | &'a self, 223 | format: FrameFormat, 224 | width: u32, 225 | height: u32, 226 | fps: u32, 227 | ) -> Result> { 228 | unsafe { 229 | let mut handle = std::mem::MaybeUninit::uninit(); 230 | let err = uvc_get_stream_ctrl_format_size( 231 | self.devh.as_ptr(), 232 | handle.as_mut_ptr(), 233 | format.into(), 234 | width as i32, 235 | height as i32, 236 | fps as i32, 237 | ) 238 | .into(); 239 | if err == Error::Success { 240 | Ok(StreamHandle { 241 | handle: handle.assume_init(), 242 | devh: self, 243 | }) 244 | } else { 245 | Err(err) 246 | } 247 | } 248 | } 249 | 250 | /// Creates a stream handle 251 | pub fn get_stream_handle_with_format( 252 | &'a self, 253 | format: StreamFormat, 254 | ) -> Result> { 255 | self.get_stream_handle_with_format_size_and_fps( 256 | format.format, 257 | format.width, 258 | format.height, 259 | format.fps, 260 | ) 261 | } 262 | } 263 | 264 | impl<'a> Drop for DeviceHandle<'a> { 265 | fn drop(&mut self) { 266 | unsafe { 267 | uvc_close(self.devh.as_ptr()); 268 | } 269 | } 270 | } 271 | 272 | #[derive(Debug)] 273 | /// Describes the device 274 | pub struct DeviceDescription { 275 | pub vendor_id: u16, 276 | pub product_id: u16, 277 | pub bcd_uvc: u16, 278 | pub serial_number: Option, 279 | pub manufacturer: Option, 280 | pub product: Option, 281 | } 282 | 283 | unsafe impl<'a> Send for FormatDescriptor<'a> {} 284 | unsafe impl<'a> Sync for FormatDescriptor<'a> {} 285 | /// Describes possible formats 286 | pub struct FormatDescriptor<'a> { 287 | format_desc: NonNull, 288 | _ph: PhantomData<&'a uvc_format_desc_t>, 289 | } 290 | 291 | #[derive(Debug, PartialEq)] 292 | /// Describes what frame or format is supported 293 | pub enum DescriptionSubtype { 294 | Undefined, 295 | InputHeader, 296 | OutputHeader, 297 | StillImageFrame, 298 | FormatUncompressed, 299 | FrameUncompressed, 300 | FormatMJPEG, 301 | FrameMJPEG, 302 | FormatMPEG2TS, 303 | FormatDV, 304 | ColorFormat, 305 | FormatFrameBased, 306 | FrameFrameBased, 307 | FormatStreamBased, 308 | } 309 | 310 | impl From for DescriptionSubtype { 311 | fn from(x: uvc_vs_desc_subtype) -> DescriptionSubtype { 312 | #[allow(non_upper_case_globals)] 313 | match x { 314 | uvc_vs_desc_subtype_UVC_VS_UNDEFINED => DescriptionSubtype::Undefined, 315 | uvc_vs_desc_subtype_UVC_VS_INPUT_HEADER => DescriptionSubtype::InputHeader, 316 | uvc_vs_desc_subtype_UVC_VS_OUTPUT_HEADER => DescriptionSubtype::OutputHeader, 317 | uvc_vs_desc_subtype_UVC_VS_STILL_IMAGE_FRAME => DescriptionSubtype::StillImageFrame, 318 | uvc_vs_desc_subtype_UVC_VS_FORMAT_UNCOMPRESSED => { 319 | DescriptionSubtype::FormatUncompressed 320 | } 321 | uvc_vs_desc_subtype_UVC_VS_FRAME_UNCOMPRESSED => DescriptionSubtype::FrameUncompressed, 322 | uvc_vs_desc_subtype_UVC_VS_FORMAT_MJPEG => DescriptionSubtype::FormatMJPEG, 323 | uvc_vs_desc_subtype_UVC_VS_FRAME_MJPEG => DescriptionSubtype::FrameMJPEG, 324 | uvc_vs_desc_subtype_UVC_VS_FORMAT_MPEG2TS => DescriptionSubtype::FormatMPEG2TS, 325 | uvc_vs_desc_subtype_UVC_VS_FORMAT_DV => DescriptionSubtype::FormatDV, 326 | uvc_vs_desc_subtype_UVC_VS_COLORFORMAT => DescriptionSubtype::ColorFormat, 327 | uvc_vs_desc_subtype_UVC_VS_FORMAT_FRAME_BASED => DescriptionSubtype::FormatFrameBased, 328 | uvc_vs_desc_subtype_UVC_VS_FRAME_FRAME_BASED => DescriptionSubtype::FrameFrameBased, 329 | uvc_vs_desc_subtype_UVC_VS_FORMAT_STREAM_BASED => DescriptionSubtype::FormatStreamBased, 330 | _ => DescriptionSubtype::Undefined, 331 | } 332 | } 333 | } 334 | 335 | impl<'a> FormatDescriptor<'a> { 336 | #[must_use] 337 | pub fn supported_formats(&self) -> FrameDescriptors { 338 | FrameDescriptors { 339 | head: unsafe { (*self.format_desc.as_ptr()).frame_descs }, 340 | _ph: PhantomData, 341 | } 342 | } 343 | 344 | #[must_use] 345 | pub fn subtype(&self) -> DescriptionSubtype { 346 | unsafe { (*self.format_desc.as_ptr()).bDescriptorSubtype }.into() 347 | } 348 | } 349 | 350 | unsafe impl<'a> Send for FormatDescriptors<'a> {} 351 | unsafe impl<'a> Sync for FormatDescriptors<'a> {} 352 | /// Iterate to get a `FormatDescriptor` 353 | pub struct FormatDescriptors<'a> { 354 | head: *const uvc_format_desc_t, 355 | _ph: PhantomData<&'a uvc_format_desc_t>, 356 | } 357 | 358 | impl<'a> Iterator for FormatDescriptors<'a> { 359 | type Item = FormatDescriptor<'a>; 360 | 361 | fn next(&mut self) -> Option> { 362 | match NonNull::new(self.head as *mut _) { 363 | None => None, 364 | Some(x) => { 365 | let current = FormatDescriptor { 366 | format_desc: x, 367 | _ph: PhantomData, 368 | }; 369 | self.head = unsafe { (*self.head).next }; 370 | Some(current) 371 | } 372 | } 373 | } 374 | } 375 | 376 | unsafe impl<'a> Send for FrameDescriptor<'a> {} 377 | unsafe impl<'a> Sync for FrameDescriptor<'a> {} 378 | #[derive(Debug)] 379 | /// Describes possible frames 380 | pub struct FrameDescriptor<'a> { 381 | frame_desc: NonNull, 382 | _ph: PhantomData<&'a uvc_frame_desc_t>, 383 | } 384 | 385 | impl<'a> FrameDescriptor<'a> { 386 | #[must_use] 387 | pub fn width(&self) -> u16 { 388 | unsafe { (*self.frame_desc.as_ptr()).wWidth } 389 | } 390 | #[must_use] 391 | pub fn height(&self) -> u16 { 392 | unsafe { (*self.frame_desc.as_ptr()).wHeight } 393 | } 394 | /// Type of frame 395 | #[must_use] 396 | pub fn subtype(&self) -> DescriptionSubtype { 397 | unsafe { (*self.frame_desc.as_ptr()).bDescriptorSubtype }.into() 398 | } 399 | /// Time in 100ns 400 | #[must_use] 401 | pub fn intervals(&self) -> &[u32] { 402 | unsafe { 403 | let intervals: *const u32 = (*self.frame_desc.as_ptr()).intervals; 404 | if intervals.is_null() { 405 | return &[]; 406 | } 407 | let mut len = 0; 408 | loop { 409 | let x = *intervals.add(len); 410 | if x == 0 { 411 | return slice::from_raw_parts::<'a>(intervals, len); 412 | } 413 | len += 1; 414 | } 415 | } 416 | } 417 | 418 | /// Duration between captures 419 | #[must_use] 420 | pub fn intervals_duration(&self) -> Vec { 421 | let times = self.intervals(); 422 | let mut durations = Vec::with_capacity(times.len()); 423 | 424 | for i in times { 425 | durations.push(Duration::from_nanos(u64::from(*i) * 100)); 426 | } 427 | 428 | durations 429 | } 430 | } 431 | 432 | unsafe impl<'a> Send for FrameDescriptors<'a> {} 433 | unsafe impl<'a> Sync for FrameDescriptors<'a> {} 434 | /// Iterate to get a `FrameDescriptor` 435 | pub struct FrameDescriptors<'a> { 436 | head: *mut uvc_frame_desc_t, 437 | _ph: PhantomData<&'a uvc_frame_desc_t>, 438 | } 439 | 440 | impl<'a> Iterator for FrameDescriptors<'a> { 441 | type Item = FrameDescriptor<'a>; 442 | 443 | fn next(&mut self) -> Option> { 444 | match NonNull::new(self.head) { 445 | None => None, 446 | Some(x) => { 447 | let current = FrameDescriptor { 448 | frame_desc: x, 449 | _ph: PhantomData, 450 | }; 451 | unsafe { self.head = (*self.head).next }; 452 | Some(current) 453 | } 454 | } 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::fmt; 3 | 4 | /// Result type of functions in this crate 5 | pub type Result = std::result::Result; 6 | 7 | /// Error codes from `libusb` 8 | #[derive(Debug, PartialEq, Copy, Clone)] 9 | pub enum Error { 10 | Success, 11 | Access, 12 | Busy, 13 | CallbackExists, 14 | Interrupted, 15 | InvalidDevice, 16 | InvalidMode, 17 | InvalidParam, 18 | IO, 19 | NotFound, 20 | NotSupported, 21 | NoDevice, 22 | NoMem, 23 | Other, 24 | Overflow, 25 | Pipe, 26 | Timeout, 27 | Unknown(uvc_sys::uvc_error_t), 28 | } 29 | 30 | impl From for Error { 31 | fn from(code: uvc_sys::uvc_error_t) -> Self { 32 | match code { 33 | uvc_sys::uvc_error_UVC_SUCCESS => Error::Success, 34 | uvc_sys::uvc_error_UVC_ERROR_ACCESS => Error::Access, 35 | uvc_sys::uvc_error_UVC_ERROR_BUSY => Error::Busy, 36 | uvc_sys::uvc_error_UVC_ERROR_CALLBACK_EXISTS => Error::CallbackExists, 37 | uvc_sys::uvc_error_UVC_ERROR_INTERRUPTED => Error::Interrupted, 38 | uvc_sys::uvc_error_UVC_ERROR_INVALID_DEVICE => Error::InvalidDevice, 39 | uvc_sys::uvc_error_UVC_ERROR_INVALID_MODE => Error::InvalidMode, 40 | uvc_sys::uvc_error_UVC_ERROR_INVALID_PARAM => Error::InvalidParam, 41 | uvc_sys::uvc_error_UVC_ERROR_IO => Error::IO, 42 | uvc_sys::uvc_error_UVC_ERROR_NOT_FOUND => Error::NotFound, 43 | uvc_sys::uvc_error_UVC_ERROR_NOT_SUPPORTED => Error::NotSupported, 44 | uvc_sys::uvc_error_UVC_ERROR_NO_DEVICE => Error::NoDevice, 45 | uvc_sys::uvc_error_UVC_ERROR_NO_MEM => Error::NoMem, 46 | uvc_sys::uvc_error_UVC_ERROR_OTHER => Error::Other, 47 | uvc_sys::uvc_error_UVC_ERROR_OVERFLOW => Error::Overflow, 48 | uvc_sys::uvc_error_UVC_ERROR_PIPE => Error::Pipe, 49 | uvc_sys::uvc_error_UVC_ERROR_TIMEOUT => Error::Timeout, 50 | x => Error::Unknown(x), 51 | } 52 | } 53 | } 54 | 55 | impl Into for Error { 56 | fn into(self) -> uvc_sys::uvc_error_t { 57 | match self { 58 | Error::Success => uvc_sys::uvc_error_UVC_SUCCESS, 59 | Error::Access => uvc_sys::uvc_error_UVC_ERROR_ACCESS, 60 | Error::Busy => uvc_sys::uvc_error_UVC_ERROR_BUSY, 61 | Error::CallbackExists => uvc_sys::uvc_error_UVC_ERROR_CALLBACK_EXISTS, 62 | Error::Interrupted => uvc_sys::uvc_error_UVC_ERROR_INTERRUPTED, 63 | Error::InvalidDevice => uvc_sys::uvc_error_UVC_ERROR_INVALID_DEVICE, 64 | Error::InvalidMode => uvc_sys::uvc_error_UVC_ERROR_INVALID_MODE, 65 | Error::InvalidParam => uvc_sys::uvc_error_UVC_ERROR_INVALID_PARAM, 66 | Error::IO => uvc_sys::uvc_error_UVC_ERROR_IO, 67 | Error::NotFound => uvc_sys::uvc_error_UVC_ERROR_NOT_FOUND, 68 | Error::NotSupported => uvc_sys::uvc_error_UVC_ERROR_NOT_SUPPORTED, 69 | Error::NoDevice => uvc_sys::uvc_error_UVC_ERROR_NO_DEVICE, 70 | Error::NoMem => uvc_sys::uvc_error_UVC_ERROR_NO_MEM, 71 | Error::Other => uvc_sys::uvc_error_UVC_ERROR_OTHER, 72 | Error::Overflow => uvc_sys::uvc_error_UVC_ERROR_OVERFLOW, 73 | Error::Pipe => uvc_sys::uvc_error_UVC_ERROR_PIPE, 74 | Error::Timeout => uvc_sys::uvc_error_UVC_ERROR_TIMEOUT, 75 | Error::Unknown(x) => x, 76 | } 77 | } 78 | } 79 | 80 | impl fmt::Display for Error { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | let strerror = unsafe { uvc_sys::uvc_strerror((*self).into()) }; 83 | if strerror.is_null() { 84 | return write!(f, "Unknown error"); 85 | } 86 | let strerr = unsafe { CStr::from_ptr(strerror) }.to_str().unwrap(); 87 | write!(f, "{}", strerr) 88 | } 89 | } 90 | impl std::error::Error for Error { 91 | fn cause(&self) -> Option<&dyn std::error::Error> { 92 | None 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/formats.rs: -------------------------------------------------------------------------------- 1 | use uvc_sys::*; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | /// Format one can request a stream to produce 5 | pub struct StreamFormat { 6 | pub width: u32, 7 | pub height: u32, 8 | pub fps: u32, 9 | pub format: FrameFormat, 10 | } 11 | 12 | #[derive(Debug, PartialEq, Copy, Clone)] 13 | /// Format of a frame 14 | pub enum FrameFormat { 15 | Unknown, 16 | Any, 17 | Uncompressed, 18 | Compressed, 19 | YUYV, 20 | UYVY, 21 | RGB, 22 | BGR, 23 | MJPEG, 24 | GRAY8, 25 | GRAY16, 26 | BY8, 27 | BA81, 28 | SGRBG8, 29 | SGBRG8, 30 | SRGGB8, 31 | SBGGR8, 32 | Count, 33 | } 34 | 35 | #[allow(non_upper_case_globals, unreachable_patterns)] 36 | impl From for FrameFormat { 37 | fn from(code: uvc_frame_format) -> Self { 38 | match code { 39 | uvc_frame_format_UVC_FRAME_FORMAT_ANY => FrameFormat::Any, 40 | uvc_frame_format_UVC_FRAME_FORMAT_UNCOMPRESSED => FrameFormat::Uncompressed, 41 | uvc_frame_format_UVC_FRAME_FORMAT_COMPRESSED => FrameFormat::Compressed, 42 | uvc_frame_format_UVC_FRAME_FORMAT_YUYV => FrameFormat::YUYV, 43 | uvc_frame_format_UVC_FRAME_FORMAT_UYVY => FrameFormat::UYVY, 44 | uvc_frame_format_UVC_FRAME_FORMAT_RGB => FrameFormat::RGB, 45 | uvc_frame_format_UVC_FRAME_FORMAT_BGR => FrameFormat::BGR, 46 | uvc_frame_format_UVC_FRAME_FORMAT_MJPEG => FrameFormat::MJPEG, 47 | uvc_frame_format_UVC_FRAME_FORMAT_GRAY8 => FrameFormat::GRAY8, 48 | uvc_frame_format_UVC_FRAME_FORMAT_GRAY16 => FrameFormat::GRAY16, 49 | uvc_frame_format_UVC_FRAME_FORMAT_BY8 => FrameFormat::BY8, 50 | uvc_frame_format_UVC_FRAME_FORMAT_BA81 => FrameFormat::BA81, 51 | uvc_frame_format_UVC_FRAME_FORMAT_SGRBG8 => FrameFormat::SGRBG8, 52 | uvc_frame_format_UVC_FRAME_FORMAT_SGBRG8 => FrameFormat::SGBRG8, 53 | uvc_frame_format_UVC_FRAME_FORMAT_SRGGB8 => FrameFormat::SRGGB8, 54 | uvc_frame_format_UVC_FRAME_FORMAT_SBGGR8 => FrameFormat::SBGGR8, 55 | 56 | uvc_frame_format_UVC_FRAME_FORMAT_COUNT => FrameFormat::Count, 57 | uvc_frame_format_UVC_FRAME_FORMAT_UNKNOWN => FrameFormat::Unknown, // unreachable 58 | _ => FrameFormat::Unknown, 59 | } 60 | } 61 | } 62 | 63 | impl Into for FrameFormat { 64 | fn into(self: FrameFormat) -> uvc_frame_format { 65 | match self { 66 | FrameFormat::Any => uvc_frame_format_UVC_FRAME_FORMAT_ANY, 67 | FrameFormat::Uncompressed => uvc_frame_format_UVC_FRAME_FORMAT_UNCOMPRESSED, 68 | FrameFormat::Compressed => uvc_frame_format_UVC_FRAME_FORMAT_COMPRESSED, 69 | FrameFormat::YUYV => uvc_frame_format_UVC_FRAME_FORMAT_YUYV, 70 | FrameFormat::UYVY => uvc_frame_format_UVC_FRAME_FORMAT_UYVY, 71 | FrameFormat::RGB => uvc_frame_format_UVC_FRAME_FORMAT_RGB, 72 | FrameFormat::BGR => uvc_frame_format_UVC_FRAME_FORMAT_BGR, 73 | FrameFormat::MJPEG => uvc_frame_format_UVC_FRAME_FORMAT_MJPEG, 74 | FrameFormat::GRAY8 => uvc_frame_format_UVC_FRAME_FORMAT_GRAY8, 75 | FrameFormat::GRAY16 => uvc_frame_format_UVC_FRAME_FORMAT_GRAY16, 76 | FrameFormat::BY8 => uvc_frame_format_UVC_FRAME_FORMAT_BY8, 77 | FrameFormat::BA81 => uvc_frame_format_UVC_FRAME_FORMAT_BA81, 78 | FrameFormat::SGRBG8 => uvc_frame_format_UVC_FRAME_FORMAT_SGRBG8, 79 | FrameFormat::SGBRG8 => uvc_frame_format_UVC_FRAME_FORMAT_SGBRG8, 80 | FrameFormat::SRGGB8 => uvc_frame_format_UVC_FRAME_FORMAT_SRGGB8, 81 | FrameFormat::SBGGR8 => uvc_frame_format_UVC_FRAME_FORMAT_SBGGR8, 82 | FrameFormat::Count => uvc_frame_format_UVC_FRAME_FORMAT_COUNT, 83 | FrameFormat::Unknown => uvc_frame_format_UVC_FRAME_FORMAT_UNKNOWN, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::NonNull; 2 | use std::slice; 3 | 4 | use crate::error::{Error, Result}; 5 | use crate::formats::FrameFormat; 6 | 7 | use uvc_sys::*; 8 | 9 | unsafe impl Send for Frame {} 10 | unsafe impl Sync for Frame {} 11 | #[derive(Debug)] 12 | /// Frame containing the image data 13 | pub struct Frame { 14 | frame: NonNull, 15 | } 16 | 17 | impl Frame { 18 | pub(crate) unsafe fn from_raw(frame: *mut uvc_frame) -> Frame { 19 | Frame { 20 | frame: NonNull::new(frame).unwrap(), 21 | } 22 | } 23 | 24 | /// Does not initialize any data 25 | unsafe fn new_with_dimensions(width: u32, height: u32, components: u32) -> Self { 26 | let frame = uvc_allocate_frame((width * height * components) as _); 27 | 28 | Frame { 29 | frame: NonNull::new(frame).unwrap(), 30 | } 31 | } 32 | 33 | /// Convert to rgb format 34 | pub fn to_rgb(&self) -> Result { 35 | let new_frame = unsafe { Frame::new_with_dimensions(self.width(), self.height(), 3) }; // RGB -> 3 bytes 36 | 37 | let err = unsafe { 38 | match self.format() { 39 | FrameFormat::MJPEG => uvc_mjpeg2rgb(self.frame.as_ptr(), new_frame.frame.as_ptr()), 40 | FrameFormat::YUYV => uvc_yuyv2rgb(self.frame.as_ptr(), new_frame.frame.as_ptr()), 41 | FrameFormat::UYVY => uvc_uyvy2rgb(self.frame.as_ptr(), new_frame.frame.as_ptr()), 42 | FrameFormat::Any => uvc_any2rgb(self.frame.as_ptr(), new_frame.frame.as_ptr()), 43 | _ => uvc_any2rgb(self.frame.as_ptr(), new_frame.frame.as_ptr()), 44 | } 45 | } 46 | .into(); 47 | 48 | if err == Error::Success { 49 | Ok(new_frame) 50 | } else { 51 | Err(err) 52 | } 53 | } 54 | 55 | /// Convert to bgr format 56 | pub fn to_bgr(&self) -> Result { 57 | let new_frame = unsafe { Frame::new_with_dimensions(self.width(), self.height(), 3) }; // BGR -> 3 bytes 58 | 59 | let err = unsafe { 60 | match self.format() { 61 | FrameFormat::YUYV => uvc_yuyv2bgr(self.frame.as_ptr(), new_frame.frame.as_ptr()), 62 | FrameFormat::UYVY => uvc_uyvy2bgr(self.frame.as_ptr(), new_frame.frame.as_ptr()), 63 | FrameFormat::Any => uvc_any2bgr(self.frame.as_ptr(), new_frame.frame.as_ptr()), 64 | _ => uvc_any2bgr(self.frame.as_ptr(), new_frame.frame.as_ptr()), 65 | } 66 | } 67 | .into(); 68 | 69 | if err == Error::Success { 70 | Ok(new_frame) 71 | } else { 72 | Err(err) 73 | } 74 | } 75 | 76 | /// Get the raw image data 77 | #[must_use] 78 | pub fn to_bytes(&self) -> &[u8] { 79 | unsafe { 80 | slice::from_raw_parts( 81 | (*self.frame.as_ptr()).data as *const u8, 82 | (*self.frame.as_ptr()).data_bytes as _, 83 | ) 84 | } 85 | } 86 | 87 | /// Width of the captured frame 88 | #[must_use] 89 | pub fn width(&self) -> u32 { 90 | unsafe { *self.frame.as_ptr() }.width 91 | } 92 | 93 | /// Heigth of the captured frame 94 | #[must_use] 95 | pub fn height(&self) -> u32 { 96 | unsafe { *self.frame.as_ptr() }.height 97 | } 98 | 99 | /// Format of the captured frame 100 | #[must_use] 101 | pub fn format(&self) -> FrameFormat { 102 | unsafe { *self.frame.as_ptr() }.frame_format.into() 103 | } 104 | 105 | /// Monotonically increasing frame number 106 | #[must_use] 107 | pub fn sequence(&self) -> u32 { 108 | unsafe { (*self.frame.as_ptr()).sequence } 109 | } 110 | 111 | /// Clones a frame 112 | pub fn duplicate(&self) -> Result { 113 | unsafe { 114 | let mut new_frame = Frame::from_raw(uvc_allocate_frame(0)); 115 | 116 | let err = uvc_duplicate_frame(self.frame.as_ptr(), new_frame.frame.as_mut()).into(); 117 | if err != Error::Success { 118 | return Err(err); 119 | } 120 | Ok(new_frame) 121 | } 122 | } 123 | } 124 | 125 | impl Drop for Frame { 126 | fn drop(&mut self) { 127 | unsafe { uvc_free_frame(self.frame.as_ptr()) } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Safe wrapper around `libuvc` 3 | 4 | This crate gives access to webcams connected to the computer, 5 | allowing one to stream and capture video. 6 | 7 | # How to use this crate 8 | 9 | ```no_run 10 | use std::sync::atomic::{AtomicUsize, Ordering}; 11 | use std::sync::Arc; 12 | use std::time::Duration; 13 | 14 | // Get a libuvc context 15 | let ctx = uvc::Context::new().expect("Could not get context"); 16 | 17 | // Get a default device 18 | let dev = ctx 19 | .find_device(None, None, None) 20 | .expect("Could not find device"); 21 | 22 | // Or create an iterator over all available devices 23 | let mut _devices = ctx.devices().expect("Could not enumerate devices"); 24 | 25 | // The device must be opened to create a handle to the device 26 | let devh = dev.open().expect("Could not open device"); 27 | 28 | // Most webcams support this format 29 | let format = uvc::StreamFormat { 30 | width: 640, 31 | height: 480, 32 | fps: 30, 33 | format: uvc::FrameFormat::YUYV, 34 | }; 35 | 36 | // Get the necessary stream information 37 | let mut streamh = devh 38 | .get_stream_handle_with_format(format) 39 | .expect("Could not open a stream with this format"); 40 | 41 | // This is a counter, increasing by one for every frame 42 | // This data must be 'static + Send + Sync to be used in 43 | // the callback used in the stream 44 | let counter = Arc::new(AtomicUsize::new(0)); 45 | 46 | // Get a stream, calling the closure as callback for every frame 47 | let stream = streamh 48 | .start_stream( 49 | |_frame, count| { 50 | count.fetch_add(1, Ordering::SeqCst); 51 | }, 52 | counter.clone(), 53 | ).expect("Could not start stream"); 54 | 55 | // Wait 10 seconds 56 | std::thread::sleep(Duration::new(10, 0)); 57 | 58 | // Explicitly stop the stream 59 | // The stream would also be stopped 60 | // when going out of scope (dropped) 61 | stream.stop(); 62 | println!("Counter: {}", counter.load(Ordering::SeqCst)); 63 | ``` 64 | See also `mirror.rs` in the examples to get an example of how to capture and display a stream 65 | */ 66 | 67 | mod context; 68 | mod controls; 69 | mod device; 70 | mod error; 71 | mod formats; 72 | mod frame; 73 | mod streaming; 74 | 75 | pub use streaming::{ActiveStream, StreamHandle}; 76 | 77 | pub use context::Context; 78 | pub use controls::{AutoExposureMode, AutoExposurePriority, ScanningMode}; 79 | pub use device::{ 80 | DescriptionSubtype, Device, DeviceDescription, DeviceHandle, DeviceList, FormatDescriptor, 81 | FormatDescriptors, FrameDescriptor, FrameDescriptors, 82 | }; 83 | pub use error::{Error, Result}; 84 | pub use formats::{FrameFormat, StreamFormat}; 85 | pub use frame::Frame; 86 | -------------------------------------------------------------------------------- /src/streaming.rs: -------------------------------------------------------------------------------- 1 | use uvc_sys::*; 2 | 3 | use crate::device::DeviceHandle; 4 | use crate::error::{Error, Result}; 5 | use crate::frame::Frame; 6 | 7 | use std::os::raw::c_void; 8 | 9 | unsafe impl<'a> Send for StreamHandle<'a> {} 10 | unsafe impl<'a> Sync for StreamHandle<'a> {} 11 | #[derive(Debug)] 12 | /// Stream handle 13 | pub struct StreamHandle<'a> { 14 | pub(crate) handle: uvc_stream_ctrl_t, 15 | pub(crate) devh: &'a DeviceHandle<'a>, 16 | } 17 | 18 | struct Vtable { 19 | func: Box, 20 | data: U, 21 | } 22 | 23 | unsafe impl<'a, U: Send + Sync> Send for ActiveStream<'a, U> {} 24 | unsafe impl<'a, U: Send + Sync> Sync for ActiveStream<'a, U> {} 25 | #[derive(Debug)] 26 | /// Active stream 27 | /// 28 | /// Dropping this stream will stop the stream 29 | pub struct ActiveStream<'a, U: Send + Sync> { 30 | devh: &'a crate::DeviceHandle<'a>, 31 | #[allow(unused)] 32 | vtable: *mut Vtable, 33 | } 34 | 35 | impl<'a, U: Send + Sync> ActiveStream<'a, U> { 36 | /// Stop the stream 37 | pub fn stop(self) { 38 | // Taking ownership of the stream, which drops it 39 | } 40 | } 41 | 42 | impl<'a, U: Send + Sync> Drop for ActiveStream<'a, U> { 43 | fn drop(&mut self) { 44 | unsafe { 45 | uvc_stop_streaming(self.devh.devh.as_ptr()); 46 | let _vtable = Box::from_raw(self.vtable); 47 | } 48 | } 49 | } 50 | 51 | unsafe extern "C" fn trampoline(frame: *mut uvc_frame, userdata: *mut c_void) 52 | where 53 | F: 'static + Send + Sync + Fn(&Frame, &mut U), 54 | U: 'static + Send + Sync, 55 | { 56 | let panic = std::panic::catch_unwind(|| { 57 | if frame.is_null() { 58 | panic!("Frame is null"); 59 | } 60 | let frame = std::mem::ManuallyDrop::new(Frame::from_raw(frame)); 61 | 62 | if userdata.is_null() { 63 | panic!("Userdata is null"); 64 | } 65 | 66 | let vtable = userdata as *mut Vtable; 67 | 68 | let func = &(*vtable).func; 69 | let data = &mut (*vtable).data; 70 | 71 | func(&frame, data); 72 | }); 73 | 74 | if panic.is_err() { 75 | eprintln!("User defined function panicked"); 76 | std::process::abort(); 77 | } 78 | } 79 | 80 | impl<'a> StreamHandle<'a> { 81 | /// Begin a stream, use the callback to save the frames 82 | /// 83 | /// This function is non-blocking 84 | pub fn start_stream(&'a mut self, cb: F, user_data: U) -> Result> 85 | where 86 | F: 'static + Send + Sync + Fn(&Frame, &mut U), 87 | U: 'static + Send + Sync, 88 | { 89 | let tuple = Box::new(Vtable:: { 90 | func: Box::new(cb), 91 | data: user_data, 92 | }); 93 | 94 | let tuple = Box::into_raw(tuple); 95 | 96 | unsafe { 97 | let err = uvc_start_streaming( 98 | self.devh.devh.as_ptr(), 99 | &mut self.handle, 100 | Some(trampoline::), 101 | tuple as *mut c_void, 102 | 0, 103 | ) 104 | .into(); 105 | if err == Error::Success { 106 | Ok(ActiveStream { 107 | devh: self.devh, 108 | vtable: tuple, 109 | }) 110 | } else { 111 | Err(err) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /uvc-src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uvc-src" 3 | version = "0.3.0" 4 | authors = ["Magnus Ulimoen "] 5 | edition = "2021" 6 | license-file = "source/LICENSE.txt" 7 | build = "build.rs" 8 | repository = "https://github.com/mulimoen/libuvc-rs" 9 | description = "Vendored version of libuvc" 10 | links = "uvcsrc" 11 | 12 | [features] 13 | jpeg = ["mozjpeg-sys"] 14 | uvc_debugging = [] 15 | 16 | [dependencies] 17 | mozjpeg-sys = { version = "2.2.1", default-features = false, optional = true } 18 | libusb-sys = { version = "0.7.0", package = "libusb1-sys" } 19 | 20 | [build-dependencies] 21 | cc = "1.0.61" 22 | -------------------------------------------------------------------------------- /uvc-src/build.rs: -------------------------------------------------------------------------------- 1 | struct Version { 2 | major: usize, 3 | minor: usize, 4 | patch: usize, 5 | } 6 | 7 | const VERSION: Version = Version { 8 | major: 0, 9 | minor: 0, 10 | patch: 6, 11 | }; 12 | 13 | fn main() { 14 | println!("cargo:rerun-if-changed=build.rs"); 15 | let mut builder = cc::Build::new(); 16 | builder.warnings(false); 17 | builder.file("source/src/ctrl.c"); 18 | builder.file("source/src/ctrl-gen.c"); 19 | builder.file("source/src/device.c"); 20 | builder.file("source/src/diag.c"); 21 | builder.file("source/src/frame.c"); 22 | builder.file("source/src/init.c"); 23 | builder.file("source/src/stream.c"); 24 | builder.file("source/src/misc.c"); 25 | 26 | let builddir: std::path::PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); 27 | let includedir = builddir.join("include"); 28 | { 29 | // Copy includedir 30 | std::fs::create_dir_all(includedir.join("libuvc")).unwrap(); 31 | { 32 | let config_h = format!( 33 | r#" 34 | #ifndef LIBUVC_CONFIG_H 35 | #define LIBUVC_CONFIG_H 36 | #define LIBUVC_VERSION_MAJOR {major} 37 | #define LIBUVC_VERSION_MINOR {minor} 38 | #define LIBUVC_VERSION_PATCH {patch} 39 | #define LIBUVC_VERSION_STR "{major}.{minor}.{patch}" 40 | #define LIBUVC_VERSION_INT (({major} << 16) | ({minor} << 8) | ({patch})) 41 | #define LIBUVC_VERSION_GTE(major, minor, patch) (LIB_UVC_VERSION_INT >= (((major) << 16) | ((minor) << 8) | (patch)) 42 | #define LIBUVC_HAS_JPEG {has_jpeg} 43 | {uvc_debug} 44 | #endif 45 | "#, 46 | major = VERSION.major, 47 | minor = VERSION.minor, 48 | patch = VERSION.patch, 49 | has_jpeg = std::env::var_os("CARGO_FEATURE_JPEG").is_some() as u8, 50 | uvc_debug = if cfg!(feature = "uvc_debugging") { 51 | "#define UVC_DEBUGGING" 52 | } else { 53 | "" 54 | }, 55 | ); 56 | use std::io::Write; 57 | let mut uvc_internal = 58 | std::fs::File::create(includedir.join("libuvc/libuvc_config.h")).unwrap(); 59 | uvc_internal.write_all(config_h.as_bytes()).unwrap(); 60 | } 61 | std::fs::copy( 62 | "source/include/libuvc/libuvc.h", 63 | includedir.join("libuvc/libuvc.h"), 64 | ) 65 | .unwrap(); 66 | std::fs::copy( 67 | "source/include/libuvc/libuvc_internal.h", 68 | includedir.join("libuvc/libuvc_internal.h"), 69 | ) 70 | .unwrap(); 71 | std::fs::copy( 72 | "source/include/utlist.h", 73 | includedir.join("libuvc/utlist.h"), 74 | ) 75 | .unwrap(); 76 | builder.include(&includedir); 77 | } 78 | 79 | if std::env::var_os("CARGO_FEATURE_JPEG").is_some() { 80 | builder.file("source/src/frame-mjpeg.c"); 81 | let jpeg_includes = std::env::var_os("DEP_JPEG_INCLUDE").unwrap(); 82 | for jpeg_include in std::env::split_paths(&jpeg_includes) { 83 | builder.include(jpeg_include); 84 | } 85 | } 86 | 87 | let usb_include = std::env::var_os("DEP_USB_1.0_INCLUDE").unwrap(); 88 | builder.include(usb_include); 89 | 90 | builder.compile("uvc"); 91 | 92 | println!("cargo:include={}", includedir.to_str().unwrap()); 93 | } 94 | -------------------------------------------------------------------------------- /uvc-src/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dummy crate to build a vendored version of libuvc 2 | 3 | // Ensure we are linking to libusb 4 | extern crate libusb_sys; 5 | // Ensure we are linking to libjpeg 6 | #[cfg(feature = "jpeg")] 7 | extern crate mozjpeg_sys; 8 | -------------------------------------------------------------------------------- /uvc-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uvc-sys" 3 | description = "Raw wrapper of libuvc" 4 | version = "0.3.0" 5 | authors = ["Magnus Ulimoen "] 6 | links = "uvc" 7 | build = "build.rs" 8 | license = "MIT" 9 | repository = "https://github.com/mulimoen/libuvc-rs.git" 10 | categories = ["external-ffi-bindings", "multimedia::video"] 11 | edition = "2021" 12 | 13 | [features] 14 | vendor = ["uvc-src"] 15 | uvc_debugging = ["uvc-src/uvc_debugging"] 16 | 17 | [dependencies] 18 | uvc-src = { path = "../uvc-src", version = "0.3.0", optional = true, features = ["jpeg"] } 19 | 20 | [build-dependencies] 21 | bindgen = "0.70.1" 22 | -------------------------------------------------------------------------------- /uvc-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | let mut includedir = None; 8 | if std::env::var_os("CARGO_FEATURE_VENDOR").is_some() { 9 | includedir = Some(std::env::var("DEP_UVCSRC_INCLUDE").unwrap()); 10 | } else { 11 | println!("cargo:rustc-link-lib=uvc"); 12 | if cfg!(target_os = "freebsd") { 13 | includedir = Some("/usr/local/include".to_owned()); 14 | } 15 | } 16 | 17 | let mut builder = bindgen::Builder::default(); 18 | 19 | if let Some(include) = includedir { 20 | builder = builder.clang_arg(format!("-I{}", include)); 21 | } 22 | 23 | let bindings = builder 24 | .header("wrapper.h") 25 | .allowlist_function("uvc_.*") 26 | .allowlist_type("uvc_.*") 27 | .generate() 28 | .expect("Failed to generate bindings"); 29 | 30 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 31 | bindings 32 | .write_to_file(out_path.join("uvc_bindings.rs")) 33 | .expect("Failed to write bindings"); 34 | } 35 | -------------------------------------------------------------------------------- /uvc-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | #[cfg(feature = "vendor")] 6 | extern crate uvc_src; 7 | 8 | include!(concat!(env!("OUT_DIR"), "/uvc_bindings.rs")); 9 | -------------------------------------------------------------------------------- /uvc-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | --------------------------------------------------------------------------------