mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
566 lines
21 KiB
Rust
566 lines
21 KiB
Rust
/*
|
|
* Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
use crate::{
|
|
ApiBackend, CameraControl, CameraFormat, CameraInfo, CaptureBackendTrait, FrameFormat,
|
|
KnownCameraControl, KnownCameraControlFlag, NokhwaError, Resolution,
|
|
};
|
|
use flume::{Receiver, Sender};
|
|
use image::{ImageBuffer, Rgb};
|
|
use ouroboros::self_referencing;
|
|
use std::{
|
|
any::Any,
|
|
borrow::Cow,
|
|
cell::{Cell, RefCell},
|
|
collections::HashMap,
|
|
mem::MaybeUninit,
|
|
sync::{atomic::AtomicUsize, Arc},
|
|
};
|
|
use uvc::{
|
|
ActiveStream, Context, DescriptionSubtype, Device, DeviceHandle, StreamFormat, StreamHandle,
|
|
};
|
|
|
|
// ignore the IDE, this compiles
|
|
/// The backend struct that interfaces with `libuvc`.
|
|
/// To see what this does, please see [`CaptureBackendTrait`]
|
|
/// # Quirks
|
|
/// - You may need administrator/superuser privileges to access a UVC device.
|
|
/// - The indexing for this backend is based off of `libuvc`'s device ordering, not the OS.
|
|
/// - You must call [create()](UVCCaptureDevice::create()) instead `new()`, some methods are auto-generated by the self-referencer and are not meant to be used.
|
|
/// - The [create()](UVCCaptureDevice::create()) method will open the device twice.
|
|
/// - Calling [`set_resolution()`](CaptureBackendTrait::set_resolution()), [`set_frame_rate()`](crate::CaptureBackendTrait::set_frame_rate()), or [`set_frame_format()`](crate::CaptureBackendTrait::set_frame_format()) each internally calls [`set_camera_format()`](crate::CaptureBackendTrait::set_camera_format()).
|
|
/// - [`frame_raw()`](crate::CaptureBackendTrait::frame_raw()) returns the same raw data as [`get_frame()`](crate::CaptureBackendTrait::frame()), a.k.a. no custom decoding required, all data is automatically RGB
|
|
/// - The [`frame_raw()`](crate::CaptureBackendTrait::frame_raw()) and by extension [`frame()`](crate::CaptureBackendTrait::frame()) functions block.
|
|
/// - Setting controls is not supported.
|
|
/// - This backend, once stream is open, will constantly collect frames. When you call [`frame()`](crate::CaptureBackendTrait::frame()) or one of its variants, it will only give you the latest frame.
|
|
/// # Safety
|
|
/// This backend requires use of `unsafe` due to the self-referencing structs involved.
|
|
/// - If [`open_stream()`](crate::CaptureBackendTrait::open_stream()) and [`frame()`](crate::CaptureBackendTrait::frame()) are called in the wrong order this will cause undefined behaviour.
|
|
/// - If internal variables `stream_handle_init` and `active_stream_init` become de-synchronized with the true reality (weather streamhandle/activestream is init or not) this will cause undefined behaviour.
|
|
#[self_referencing]
|
|
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-uvc")))]
|
|
#[deprecated(
|
|
since = "0.10",
|
|
note = "Use one of the native backends instead(V4L, AVF, MSMF) or OpenCV"
|
|
)]
|
|
pub struct UVCCaptureDevice<'a> {
|
|
camera_format: CameraFormat,
|
|
camera_info: CameraInfo<'a>,
|
|
frame_receiver: Receiver<Vec<u8>>,
|
|
frame_sender: Sender<Vec<u8>>,
|
|
stream_handle_init: Cell<bool>,
|
|
active_stream_init: Cell<bool>,
|
|
context: Context<'a>,
|
|
#[not_covariant]
|
|
#[borrows(context)]
|
|
device: Device<'this>,
|
|
#[not_covariant]
|
|
#[borrows(device)]
|
|
device_handle: DeviceHandle<'this>,
|
|
stream_handle: RefCell<MaybeUninit<StreamHandle<'a>>>,
|
|
active_stream: RefCell<MaybeUninit<ActiveStream<'a, Arc<AtomicUsize>>>>,
|
|
}
|
|
|
|
impl<'a> UVCCaptureDevice<'a> {
|
|
/// Creates a UVC Camera device with optional [`CameraFormat`].
|
|
/// If `camera_format` is `None`, it will be spawned with with 640x480@15 FPS, MJPEG [`CameraFormat`] default.
|
|
/// # Panics
|
|
/// This operation may panic! If the UVC Context fails to retrieve the device from the gotten IDs, this operation will panic.
|
|
/// # Errors
|
|
/// This may error when the `libuvc` backend fails to retrieve the device or its data.
|
|
pub fn create(index: usize, cam_fmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
|
|
let context = match Context::new() {
|
|
Ok(ctx) => ctx,
|
|
Err(why) => {
|
|
return Err(NokhwaError::OpenDeviceError(
|
|
index.to_string(),
|
|
why.to_string(),
|
|
))
|
|
}
|
|
};
|
|
|
|
let (camera_info, frame_receiver, frame_sender) = {
|
|
let device_list = match context.devices() {
|
|
Ok(device_list) => device_list,
|
|
Err(why) => {
|
|
return Err(NokhwaError::OpenDeviceError(
|
|
index.to_string(),
|
|
why.to_string(),
|
|
))
|
|
}
|
|
};
|
|
|
|
let device = match device_list.into_iter().nth(index) {
|
|
Some(d) => d,
|
|
None => {
|
|
return Err(NokhwaError::OpenDeviceError(
|
|
index.to_string(),
|
|
"Not Found".to_string(),
|
|
))
|
|
}
|
|
};
|
|
|
|
let device_desc = match device.description() {
|
|
Ok(desc) => desc,
|
|
Err(why) => {
|
|
return Err(NokhwaError::OpenDeviceError(
|
|
index.to_string(),
|
|
why.to_string(),
|
|
))
|
|
}
|
|
};
|
|
|
|
let device_name = match (device_desc.manufacturer, device_desc.product) {
|
|
(Some(manu), Some(prod)) => {
|
|
format!("{} {}", manu, prod)
|
|
}
|
|
(_, Some(prod)) => prod,
|
|
(Some(manu), _) => {
|
|
format!(
|
|
"{}:{} {}",
|
|
device_desc.vendor_id, device_desc.product_id, manu
|
|
)
|
|
}
|
|
(_, _) => {
|
|
format!("{}:{}", device_desc.vendor_id, device_desc.product_id)
|
|
}
|
|
};
|
|
|
|
let camera_info = CameraInfo::new(
|
|
device_name,
|
|
"".to_string(),
|
|
format!("{}:{}", device_desc.vendor_id, device_desc.product_id),
|
|
index,
|
|
);
|
|
|
|
let (frame_sender, frame_receiver) = {
|
|
let (a, b) = flume::unbounded::<Vec<u8>>();
|
|
(a, b)
|
|
};
|
|
(camera_info, frame_receiver, frame_sender)
|
|
};
|
|
|
|
let camera_format = match cam_fmt {
|
|
Some(cfmt) => cfmt,
|
|
None => CameraFormat::default(),
|
|
};
|
|
|
|
Ok(UVCCaptureDeviceBuilder {
|
|
camera_format,
|
|
camera_info,
|
|
frame_receiver,
|
|
frame_sender,
|
|
context,
|
|
stream_handle_init: Cell::new(false),
|
|
active_stream_init: Cell::new(false),
|
|
device_builder: |context_builder| {
|
|
context_builder
|
|
.devices()
|
|
.unwrap()
|
|
.into_iter()
|
|
.nth(index)
|
|
.unwrap()
|
|
},
|
|
device_handle_builder: |device_builder| device_builder.open().unwrap(),
|
|
stream_handle: RefCell::new(MaybeUninit::uninit()),
|
|
active_stream: RefCell::new(MaybeUninit::uninit()),
|
|
}
|
|
.build())
|
|
}
|
|
|
|
/// Create a UVC Camera with desired settings.
|
|
/// # Panics
|
|
/// This operation may panic! If the UVC Context fails to retrieve the device from the gotten IDs, this operation will panic.
|
|
/// # Errors
|
|
/// This may error when the `libuvc` backend fails to retrieve the device or its data.
|
|
pub fn create_with(
|
|
index: usize,
|
|
width: u32,
|
|
height: u32,
|
|
fps: u32,
|
|
fourcc: FrameFormat,
|
|
) -> Result<Self, NokhwaError> {
|
|
let camera_format = Some(CameraFormat::new_from(width, height, fourcc, fps));
|
|
UVCCaptureDevice::create(index, camera_format)
|
|
}
|
|
}
|
|
|
|
// IDE Autocomplete ends here. Do not be afraid it your IDE does not show completion.
|
|
// Here are some docs to help you out: https://docs.rs/ouroboros/0.9.3/ouroboros/attr.self_referencing.html
|
|
impl<'a> CaptureBackendTrait for UVCCaptureDevice<'a> {
|
|
fn backend(&self) -> ApiBackend {
|
|
ApiBackend::UniversalVideoClass
|
|
}
|
|
|
|
fn camera_info(&self) -> &CameraInfo {
|
|
self.borrow_camera_info()
|
|
}
|
|
|
|
fn camera_format(&self) -> CameraFormat {
|
|
*self.borrow_camera_format()
|
|
}
|
|
|
|
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
|
|
let prev_fmt = *self.borrow_camera_format();
|
|
|
|
self.with_camera_format_mut(|cfmt| {
|
|
*cfmt = new_fmt;
|
|
});
|
|
|
|
let is_streamh_some = self.borrow_stream_handle_init().get();
|
|
|
|
if is_streamh_some {
|
|
return match self.open_stream() {
|
|
Ok(_) => Ok(()),
|
|
Err(why) => {
|
|
// revert
|
|
self.with_camera_format_mut(|cfmt| {
|
|
*cfmt = prev_fmt;
|
|
});
|
|
Err(NokhwaError::SetPropertyError {
|
|
property: "CameraFormat".to_string(),
|
|
value: new_fmt.to_string(),
|
|
error: why.to_string(),
|
|
})
|
|
}
|
|
};
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
fn compatible_list_by_resolution(
|
|
&mut self,
|
|
fourcc: FrameFormat,
|
|
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
|
|
let mut resolution_fps_map: HashMap<Resolution, Vec<u32>> = HashMap::new();
|
|
for fmt in self.with_device_handle(|devh| devh).supported_formats() {
|
|
for frame_desc in fmt.supported_formats() {
|
|
// FIXME: Verify that this is correct way to interpret DescriptionSubtype!
|
|
let format = match frame_desc.subtype() {
|
|
DescriptionSubtype::FormatMJPEG | DescriptionSubtype::FrameMJPEG => {
|
|
FrameFormat::MJPEG
|
|
}
|
|
DescriptionSubtype::FormatUncompressed
|
|
| DescriptionSubtype::FrameUncompressed => FrameFormat::YUYV,
|
|
_ => continue,
|
|
};
|
|
|
|
if format != fourcc {
|
|
continue;
|
|
}
|
|
|
|
let resolution =
|
|
Resolution::new(frame_desc.width().into(), frame_desc.height().into());
|
|
let fps: Vec<u32> = frame_desc
|
|
.intervals_duration()
|
|
.into_iter()
|
|
.map(|duration| (1000 / duration.as_millis()) as u32)
|
|
.collect();
|
|
resolution_fps_map.insert(resolution, fps);
|
|
}
|
|
}
|
|
Ok(resolution_fps_map)
|
|
}
|
|
|
|
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
|
|
let mut frameformats = vec![];
|
|
for fmt in self.with_device_handle(|devh| devh).supported_formats() {
|
|
for frame_desc in fmt.supported_formats() {
|
|
// FIXME: Verify that this is correct way to interpret DescriptionSubtype!
|
|
match frame_desc.subtype() {
|
|
DescriptionSubtype::FormatMJPEG | DescriptionSubtype::FrameMJPEG => {
|
|
frameformats.push(FrameFormat::MJPEG);
|
|
}
|
|
DescriptionSubtype::FormatUncompressed
|
|
| DescriptionSubtype::FrameUncompressed => frameformats.push(FrameFormat::YUYV),
|
|
_ => continue,
|
|
};
|
|
}
|
|
}
|
|
|
|
frameformats.sort();
|
|
frameformats.dedup();
|
|
Ok(frameformats)
|
|
}
|
|
|
|
fn resolution(&self) -> Resolution {
|
|
self.borrow_camera_format().resolution()
|
|
}
|
|
|
|
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
|
|
let mut current_format = *self.borrow_camera_format();
|
|
current_format.set_resolution(new_res);
|
|
self.set_camera_format(current_format)
|
|
}
|
|
|
|
fn frame_rate(&self) -> u32 {
|
|
self.borrow_camera_format().frame_rate()
|
|
}
|
|
|
|
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
|
|
let mut current_format = *self.borrow_camera_format();
|
|
current_format.set_frame_rate(new_fps);
|
|
self.set_camera_format(current_format)
|
|
}
|
|
|
|
fn frame_format(&self) -> FrameFormat {
|
|
self.borrow_camera_format().format()
|
|
}
|
|
|
|
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
|
|
let mut current_format = *self.borrow_camera_format();
|
|
current_format.set_format(fourcc);
|
|
self.set_camera_format(current_format)
|
|
}
|
|
|
|
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControl>, NokhwaError> {
|
|
Ok(vec![
|
|
KnownCameraControl::Exposure,
|
|
KnownCameraControl::Focus,
|
|
])
|
|
}
|
|
|
|
fn camera_control(&self, control: KnownCameraControl) -> Result<CameraControl, NokhwaError> {
|
|
match control {
|
|
KnownCameraControl::Focus => match self.with_device_handle(|x| x).exposure_rel() {
|
|
Ok(v) => {
|
|
let v: i8 = v;
|
|
match CameraControl::new(
|
|
control,
|
|
i32::from(i8::MIN),
|
|
i32::from(i8::MAX),
|
|
i32::from(v),
|
|
1_i32,
|
|
i32::from(v),
|
|
KnownCameraControlFlag::Automatic,
|
|
true,
|
|
) {
|
|
Ok(cc) => Ok(cc),
|
|
Err(why) => Err(NokhwaError::GetPropertyError {
|
|
property: control.to_string(),
|
|
error: why.to_string(),
|
|
}),
|
|
}
|
|
}
|
|
Err(why) => Err(NokhwaError::GetPropertyError {
|
|
property: control.to_string(),
|
|
error: why.to_string(),
|
|
}),
|
|
},
|
|
_ => Err(NokhwaError::GetPropertyError {
|
|
property: control.to_string(),
|
|
error: "Not Supported".to_string(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn set_camera_control(&mut self, _control: CameraControl) -> Result<(), NokhwaError> {
|
|
Err(NokhwaError::UnsupportedOperationError(
|
|
ApiBackend::UniversalVideoClass,
|
|
))
|
|
}
|
|
|
|
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
|
|
Err(NokhwaError::UnsupportedOperationError(
|
|
ApiBackend::UniversalVideoClass,
|
|
))
|
|
}
|
|
|
|
fn raw_camera_control(&self, _control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
|
|
Err(NokhwaError::UnsupportedOperationError(
|
|
ApiBackend::UniversalVideoClass,
|
|
))
|
|
}
|
|
|
|
fn set_raw_camera_control(
|
|
&mut self,
|
|
_control: &dyn Any,
|
|
_value: &dyn Any,
|
|
) -> Result<(), NokhwaError> {
|
|
Err(NokhwaError::UnsupportedOperationError(
|
|
ApiBackend::UniversalVideoClass,
|
|
))
|
|
}
|
|
|
|
fn open_stream(&mut self) -> Result<(), NokhwaError> {
|
|
let ret: Result<(), NokhwaError> = self.with_mut(|fields| {
|
|
let stream_format: StreamFormat = StreamFormat {
|
|
width: (*fields.camera_format).width(),
|
|
height: (*fields.camera_format).height(),
|
|
fps: (*fields.camera_format).frame_rate(),
|
|
format: (*fields.camera_format).format().into(),
|
|
};
|
|
|
|
// first, drop the existing stream by setting it to None
|
|
{
|
|
if fields.active_stream_init.get() {
|
|
let innard_value = fields.active_stream.replace(MaybeUninit::uninit());
|
|
unsafe {
|
|
std::mem::drop(innard_value.assume_init());
|
|
};
|
|
fields.active_stream_init.set(false);
|
|
}
|
|
|
|
if fields.stream_handle_init.get() {
|
|
let innard_value = fields.stream_handle.replace(MaybeUninit::uninit());
|
|
unsafe {
|
|
std::mem::drop(innard_value.assume_init());
|
|
};
|
|
fields.stream_handle_init.set(false);
|
|
}
|
|
}
|
|
// second, set the stream handle according to the streamformat
|
|
match fields
|
|
.device_handle
|
|
.get_stream_handle_with_format(stream_format)
|
|
{
|
|
Ok(streamh) => match fields.stream_handle.try_borrow_mut() {
|
|
Ok(mut streamh_raw) => {
|
|
*streamh_raw = MaybeUninit::new(streamh);
|
|
fields.stream_handle_init.set(true);
|
|
}
|
|
Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
|
|
},
|
|
Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
|
|
}
|
|
Ok(())
|
|
});
|
|
|
|
if ret.is_err() {
|
|
return ret;
|
|
}
|
|
|
|
let ret_2: Result<(), NokhwaError> = self.with(|fields| {
|
|
// finally, get the active stream
|
|
let counter = Arc::new(AtomicUsize::new(0));
|
|
let frame_sender: Sender<Vec<u8>> = self.with_frame_sender(Clone::clone);
|
|
let streamh = unsafe {
|
|
let raw_ptr =
|
|
(*fields.stream_handle.borrow_mut()).as_ptr() as *mut MaybeUninit<StreamHandle>;
|
|
let assume_inited: *mut MaybeUninit<StreamHandle<'static>> =
|
|
raw_ptr.cast::<MaybeUninit<uvc::StreamHandle>>();
|
|
&mut *assume_inited
|
|
};
|
|
let streamh_init = unsafe {
|
|
match streamh.as_mut_ptr().as_mut() {
|
|
Some(sth) => sth,
|
|
None => {
|
|
return Err(NokhwaError::OpenStreamError(
|
|
"Failed to get mutable raw pointer to stream handle!".to_string(),
|
|
))
|
|
}
|
|
}
|
|
};
|
|
|
|
let active_stream = match streamh_init.start_stream(
|
|
move |frame, _count| {
|
|
let vec_frame = frame.to_rgb().unwrap().to_bytes().to_vec();
|
|
if frame_sender.send(vec_frame).is_err() {
|
|
// do nothing
|
|
}
|
|
},
|
|
counter,
|
|
) {
|
|
Ok(active) => active,
|
|
Err(why) => return Err(NokhwaError::OpenStreamError(why.to_string())),
|
|
};
|
|
*fields.active_stream.borrow_mut() = MaybeUninit::new(active_stream);
|
|
Ok(())
|
|
});
|
|
|
|
if ret_2.is_err() {
|
|
return ret_2;
|
|
}
|
|
self.borrow_active_stream_init().set(true);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn is_stream_open(&self) -> bool {
|
|
self.with_active_stream_init(Cell::get)
|
|
}
|
|
|
|
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
|
|
let resolution: Resolution = self.borrow_camera_format().resolution();
|
|
|
|
let data = match self.frame_raw() {
|
|
Ok(d) => d,
|
|
Err(why) => return Err(why),
|
|
};
|
|
|
|
let imagebuf: ImageBuffer<Rgb<u8>, Vec<u8>> =
|
|
match ImageBuffer::from_vec(resolution.width(), resolution.height(), data.to_vec()) {
|
|
Some(img) => img,
|
|
None => {
|
|
return Err(NokhwaError::ReadFrameError(
|
|
"ImageBuffer too small! This is probably a bug, please report it!"
|
|
.to_string(),
|
|
))
|
|
}
|
|
};
|
|
|
|
Ok(imagebuf)
|
|
}
|
|
|
|
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
|
|
// assertions
|
|
if !self.borrow_active_stream_init().get() {
|
|
return Err(NokhwaError::ReadFrameError(
|
|
"Please call `open_stream()` first!".to_string(),
|
|
));
|
|
}
|
|
|
|
let f_recv = self.borrow_frame_receiver();
|
|
let messages_iter = f_recv.drain();
|
|
match messages_iter.last() {
|
|
Some(msg) => Ok(Cow::from(msg)),
|
|
None => match f_recv.recv() {
|
|
Ok(msg) => Ok(Cow::from(msg)),
|
|
Err(why) => {
|
|
return Err(NokhwaError::ReadFrameError(format!(
|
|
"All sender dropped: {}",
|
|
why.to_string()
|
|
)))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
|
|
self.with(|fields| {
|
|
if fields.active_stream_init.get() {
|
|
let innard_value = fields.active_stream.replace(MaybeUninit::uninit());
|
|
unsafe {
|
|
std::mem::drop(innard_value.assume_init());
|
|
};
|
|
fields.active_stream_init.set(false);
|
|
}
|
|
|
|
if fields.stream_handle_init.get() {
|
|
let innard_value = fields.stream_handle.replace(MaybeUninit::uninit());
|
|
unsafe {
|
|
std::mem::drop(innard_value.assume_init());
|
|
};
|
|
fields.stream_handle_init.set(false);
|
|
}
|
|
});
|
|
Ok(())
|
|
}
|
|
}
|