use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::time::{Duration, SystemTime};
use std::{fmt, ptr::NonNull};
use libuvc_sys::{
uvc_close, uvc_context_t, uvc_device_handle_t, uvc_device_t, uvc_exit, uvc_find_device,
uvc_format_desc_t, uvc_frame_desc_t, uvc_frame_t, uvc_get_format_descs,
uvc_get_stream_ctrl_format_size, uvc_init, uvc_open, uvc_ref_device, uvc_stream_close,
uvc_stream_ctrl_t, uvc_stream_get_frame, uvc_stream_handle_t, uvc_stream_open_ctrl,
uvc_stream_start, uvc_stream_stop, uvc_strerror, uvc_unref_device,
};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(i32)]
pub enum Error {
Io = libuvc_sys::uvc_error_UVC_ERROR_IO,
InvalidParam = libuvc_sys::uvc_error_UVC_ERROR_INVALID_PARAM,
Access = libuvc_sys::uvc_error_UVC_ERROR_ACCESS,
NoDevice = libuvc_sys::uvc_error_UVC_ERROR_NO_DEVICE,
NotFound = libuvc_sys::uvc_error_UVC_ERROR_NOT_FOUND,
Busy = libuvc_sys::uvc_error_UVC_ERROR_BUSY,
Timeout = libuvc_sys::uvc_error_UVC_ERROR_TIMEOUT,
Overflow = libuvc_sys::uvc_error_UVC_ERROR_OVERFLOW,
Pipe = libuvc_sys::uvc_error_UVC_ERROR_PIPE,
Interrupted = libuvc_sys::uvc_error_UVC_ERROR_INTERRUPTED,
NoMem = libuvc_sys::uvc_error_UVC_ERROR_NO_MEM,
NotSupported = libuvc_sys::uvc_error_UVC_ERROR_NOT_SUPPORTED,
InvalidDevice = libuvc_sys::uvc_error_UVC_ERROR_INVALID_DEVICE,
InvalidMode = libuvc_sys::uvc_error_UVC_ERROR_INVALID_MODE,
CallbackExists = libuvc_sys::uvc_error_UVC_ERROR_CALLBACK_EXISTS,
Other = libuvc_sys::uvc_error_UVC_ERROR_OTHER,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = unsafe { CStr::from_ptr(uvc_strerror(*self as i32)) };
write!(f, "{}", msg.to_string_lossy())
}
}
impl From<i32> for Error {
fn from(value: i32) -> Self {
match value {
libuvc_sys::uvc_error_UVC_ERROR_IO => Self::Io,
libuvc_sys::uvc_error_UVC_ERROR_INVALID_PARAM => Self::InvalidParam,
libuvc_sys::uvc_error_UVC_ERROR_ACCESS => Self::Access,
libuvc_sys::uvc_error_UVC_ERROR_NO_DEVICE => Self::NoDevice,
libuvc_sys::uvc_error_UVC_ERROR_NOT_FOUND => Self::NotFound,
libuvc_sys::uvc_error_UVC_ERROR_BUSY => Self::Busy,
libuvc_sys::uvc_error_UVC_ERROR_TIMEOUT => Self::Timeout,
libuvc_sys::uvc_error_UVC_ERROR_OVERFLOW => Self::Overflow,
libuvc_sys::uvc_error_UVC_ERROR_PIPE => Self::Pipe,
libuvc_sys::uvc_error_UVC_ERROR_INTERRUPTED => Self::Interrupted,
libuvc_sys::uvc_error_UVC_ERROR_NO_MEM => Self::NoMem,
libuvc_sys::uvc_error_UVC_ERROR_NOT_SUPPORTED => Self::NotSupported,
libuvc_sys::uvc_error_UVC_ERROR_INVALID_DEVICE => Self::InvalidDevice,
libuvc_sys::uvc_error_UVC_ERROR_INVALID_MODE => Self::InvalidMode,
libuvc_sys::uvc_error_UVC_ERROR_CALLBACK_EXISTS => Self::CallbackExists,
_ => Self::Other,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u32)]
pub enum VsDescSubtype {
Undefined = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_UNDEFINED,
InputHeader = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_INPUT_HEADER,
OutputHeader = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_OUTPUT_HEADER,
StillImageFrame = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_STILL_IMAGE_FRAME,
FormatUncompressed = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_UNCOMPRESSED,
FrameUncompressed = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_UNCOMPRESSED,
FormatMjpeg = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_MJPEG,
FrameMjpeg = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_MJPEG,
FormatMpeg2ts = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_MPEG2TS,
FormatDv = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_DV,
Colorformat = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_COLORFORMAT,
FormatFrameBased = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_FRAME_BASED,
FrameFrameBased = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_FRAME_BASED,
FormatStreamBased = libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_STREAM_BASED,
}
impl From<u32> for VsDescSubtype {
fn from(value: u32) -> Self {
match value {
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_UNDEFINED => Self::Undefined,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_INPUT_HEADER => Self::InputHeader,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_OUTPUT_HEADER => Self::OutputHeader,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_STILL_IMAGE_FRAME => Self::StillImageFrame,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_UNCOMPRESSED => Self::FormatUncompressed,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_UNCOMPRESSED => Self::FrameUncompressed,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_MJPEG => Self::FormatMjpeg,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_MJPEG => Self::FrameMjpeg,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_MPEG2TS => Self::FormatMpeg2ts,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_DV => Self::FormatDv,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_COLORFORMAT => Self::Colorformat,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_FRAME_BASED => Self::FormatFrameBased,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FRAME_FRAME_BASED => Self::FrameFrameBased,
libuvc_sys::uvc_vs_desc_subtype_UVC_VS_FORMAT_STREAM_BASED => Self::FormatStreamBased,
_ => panic!("unknown uvc_vs_desc_subtype `{value}`"),
}
}
}
#[derive(Debug)]
#[repr(u32)]
pub enum FrameFormat {
UnknownOrAny = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_UNKNOWN,
Uncompressed = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_UNCOMPRESSED,
Compressed = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_COMPRESSED,
Yuyv = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_YUYV,
Uyvy = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_UYVY,
Rgb = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_RGB,
Bgr = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_BGR,
Mjpeg = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_MJPEG,
H264 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_H264,
Gray8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_GRAY8,
Gray16 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_GRAY16,
By8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_BY8,
Ba81 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_BA81,
Sgrbg8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_SGRBG8,
Sgbrg8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_SGBRG8,
Srggb8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_SRGGB8,
Sbggr8 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_SBGGR8,
Nv12 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_NV12,
P010 = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_P010,
Count = libuvc_sys::uvc_frame_format_UVC_FRAME_FORMAT_COUNT,
}
trait I32Err {
fn to_result(self) -> Result<(), Error>;
}
impl I32Err for i32 {
fn to_result(self) -> Result<(), Error> {
if self == 0 {
Ok(())
} else {
Err(Error::from(self))
}
}
}
#[derive(Debug)]
pub struct Context(NonNull<uvc_context_t>);
impl Drop for Context {
fn drop(&mut self) {
unsafe {
uvc_exit(self.0.as_mut());
}
}
}
impl Context {
pub fn new() -> Result<Self, Error> {
let mut ptr = std::ptr::null_mut();
unsafe {
uvc_init(&mut ptr, std::ptr::null_mut()).to_result()?;
}
Ok(Self(NonNull::new(ptr).ok_or(Error::Other)?))
}
pub fn find_device(
&self,
vid: Option<u16>,
pid: Option<u16>,
sn: Option<&str>,
) -> Result<Device<'_>, Error> {
let mut ptr = std::ptr::null_mut();
let vid = vid.unwrap_or_default().into();
let pid = pid.unwrap_or_default().into();
let sn = sn.map(|sn| CString::new(sn).unwrap());
let sn_ptr = sn.as_ref().map(|sn| sn.as_ptr()).unwrap_or_default();
unsafe {
uvc_find_device(self.0.as_ptr(), &mut ptr, vid, pid, sn_ptr).to_result()?;
}
drop(sn);
Ok(Device {
ctx: self,
ptr: NonNull::new(ptr).ok_or(Error::Other)?,
})
}
}
#[derive(Debug)]
pub struct Device<'a> {
ctx: &'a Context,
ptr: NonNull<uvc_device_t>,
}
impl<'a> Drop for Device<'a> {
fn drop(&mut self) {
unsafe {
uvc_unref_device(self.ptr.as_mut());
}
}
}
impl<'a> Clone for Device<'a> {
fn clone(&self) -> Self {
unsafe {
uvc_ref_device(self.ptr.as_ptr());
}
Self {
ctx: self.ctx,
ptr: self.ptr,
}
}
}
impl<'a> Device<'a> {
pub fn open(&self) -> Result<DeviceHandle<'_>, Error> {
let mut ptr = std::ptr::null_mut();
unsafe {
uvc_open(self.ptr.as_ptr(), &mut ptr).to_result()?;
}
Ok(DeviceHandle {
_dev: self,
ptr: NonNull::new(ptr).ok_or(Error::Other)?,
})
}
}
#[derive(Debug)]
pub struct DeviceHandle<'a> {
_dev: &'a Device<'a>,
ptr: NonNull<uvc_device_handle_t>,
}
impl<'a> Drop for DeviceHandle<'a> {
fn drop(&mut self) {
unsafe {
uvc_close(self.ptr.as_mut());
}
}
}
impl<'a> DeviceHandle<'a> {
pub fn format_descs(&self) -> impl '_ + Iterator<Item = FormatDesc<'_>> {
FormatDescIter {
devh: self,
ptr: unsafe { uvc_get_format_descs(self.ptr.as_ptr()) },
}
}
pub fn get_stream_ctrl_format_size(
&self,
format: FrameFormat,
width: Option<i32>,
height: Option<i32>,
fps: Option<i32>,
) -> Result<StreamCtrl, Error> {
let mut ctrl = StreamCtrl(uvc_stream_ctrl_t {
bmHint: 0,
bFormatIndex: 0,
bFrameIndex: 0,
dwFrameInterval: 0,
wKeyFrameRate: 0,
wPFrameRate: 0,
wCompQuality: 0,
wCompWindowSize: 0,
wDelay: 0,
dwMaxVideoFrameSize: 0,
dwMaxPayloadTransferSize: 0,
dwClockFrequency: 0,
bmFramingInfo: 0,
bPreferredVersion: 0,
bMinVersion: 0,
bMaxVersion: 0,
bInterfaceNumber: 0,
});
let width = width.unwrap_or_default();
let height = height.unwrap_or_default();
let fps = fps.unwrap_or_default();
unsafe {
uvc_get_stream_ctrl_format_size(
self.ptr.as_ptr(),
&mut ctrl.0,
format as u32,
width,
height,
fps,
)
.to_result()?;
}
Ok(ctrl)
}
pub fn open_stream(&self, ctrl: &mut StreamCtrl) -> Result<StreamHandle<'_>, Error> {
let mut strmh: *mut uvc_stream_handle_t = std::ptr::null_mut();
unsafe {
uvc_stream_open_ctrl(self.ptr.as_ptr(), &mut strmh, &mut ctrl.0 as *mut _)
.to_result()?;
}
Ok(StreamHandle {
_devh: self,
ptr: NonNull::new(strmh).ok_or(Error::Other)?,
})
}
}
struct FormatDescIter<'a> {
devh: &'a DeviceHandle<'a>,
ptr: *const uvc_format_desc_t,
}
impl<'a> Iterator for FormatDescIter<'a> {
type Item = FormatDesc<'a>;
fn next(&mut self) -> Option<Self::Item> {
let ptr = NonNull::new(self.ptr as *mut uvc_format_desc_t)?;
self.ptr = unsafe { ptr.as_ref().next };
Some(FormatDesc {
_devh: self.devh,
ptr,
})
}
}
#[derive(Debug)]
pub struct FormatDesc<'a> {
_devh: &'a DeviceHandle<'a>,
ptr: NonNull<uvc_format_desc_t>,
}
impl<'a> FormatDesc<'a> {
pub fn descriptor_subtype(&self) -> VsDescSubtype {
let subtype = unsafe { self.ptr.as_ref().bDescriptorSubtype };
subtype.into()
}
pub fn frame_descs(&self) -> impl '_ + Iterator<Item = FrameDesc<'_>> {
FrameDescIter {
fmt: self,
ptr: unsafe { self.ptr.as_ref().frame_descs },
}
}
}
struct FrameDescIter<'a> {
fmt: &'a FormatDesc<'a>,
ptr: *const uvc_frame_desc_t,
}
impl<'a> Iterator for FrameDescIter<'a> {
type Item = FrameDesc<'a>;
fn next(&mut self) -> Option<Self::Item> {
let ptr = NonNull::new(self.ptr as *mut uvc_frame_desc_t)?;
self.ptr = unsafe { ptr.as_ref().next };
Some(FrameDesc {
_fmt: self.fmt,
ptr,
})
}
}
#[derive(Debug)]
pub struct FrameDesc<'a> {
_fmt: &'a FormatDesc<'a>,
ptr: NonNull<uvc_frame_desc_t>,
}
impl<'a> FrameDesc<'a> {
pub fn descriptor_subtype(&self) -> VsDescSubtype {
let subtype = unsafe { self.ptr.as_ref().bDescriptorSubtype };
subtype.into()
}
pub fn width(&self) -> u16 {
unsafe { self.ptr.as_ref().wWidth }
}
pub fn height(&self) -> u16 {
unsafe { self.ptr.as_ref().wHeight }
}
pub fn default_frame_interval(&self) -> u32 {
unsafe { self.ptr.as_ref().dwDefaultFrameInterval }
}
}
#[derive(Debug)]
pub struct StreamCtrl(uvc_stream_ctrl_t);
#[derive(Debug)]
pub struct Frame<'a> {
_strmh: PhantomData<&'a StreamHandle<'a>>,
ptr: NonNull<uvc_frame_t>,
}
impl<'a> Frame<'a> {
pub fn data(&self) -> &[u8] {
unsafe {
let inner = self.ptr.as_ref();
std::slice::from_raw_parts(inner.data as *const u8, inner.data_bytes)
}
}
pub fn capture_time(&self) -> SystemTime {
let timeval = unsafe {
let inner = self.ptr.as_ref();
inner.capture_time
};
let ns = timeval.tv_usec * 1000;
let duration = Duration::new(timeval.tv_sec.try_into().unwrap(), ns.try_into().unwrap());
SystemTime::UNIX_EPOCH + duration
}
pub fn width(&self) -> u32 {
unsafe { self.ptr.as_ref().width }
}
pub fn height(&self) -> u32 {
unsafe { self.ptr.as_ref().height }
}
}
#[derive(Debug)]
pub struct StreamHandle<'a> {
_devh: &'a DeviceHandle<'a>,
ptr: NonNull<uvc_stream_handle_t>,
}
impl<'a> Drop for StreamHandle<'a> {
fn drop(&mut self) {
unsafe {
uvc_stream_close(self.ptr.as_mut());
}
}
}
impl<'a> StreamHandle<'a> {
pub fn start(&self) -> Result<(), Error> {
unsafe {
uvc_stream_start(self.ptr.as_ptr(), None, std::ptr::null_mut(), 0).to_result()?;
}
Ok(())
}
pub fn stop(&self) -> Result<(), Error> {
unsafe {
uvc_stream_stop(self.ptr.as_ptr()).to_result()?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub enum Timeout {
NoWait,
Forever,
Duration(Duration),
}
impl From<Duration> for Timeout {
fn from(value: Duration) -> Self {
Self::Duration(value)
}
}
impl<'a> StreamHandle<'a> {
pub fn frame<T: Into<Timeout>>(&self, timeout: T) -> Result<Frame<'_>, Error> {
let timeout = match timeout.into() {
Timeout::NoWait => -1,
Timeout::Forever => 0,
Timeout::Duration(d) => d.as_micros().try_into().unwrap(),
};
let mut ptr = std::ptr::null_mut();
unsafe {
uvc_stream_get_frame(self.ptr.as_ptr(), &mut ptr as *mut _, timeout).to_result()?;
}
let frame = Frame {
_strmh: PhantomData,
ptr: NonNull::new(ptr).ok_or(Error::Other)?,
};
let owned = unsafe { frame.ptr.as_ref().library_owns_data };
if 0 == owned {
panic!();
}
Ok(frame)
}
}