mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-13 02:54:26 +08:00
chore: vendor trimmed v4l2r capture crate
This commit is contained in:
66
libs/v4l2r/src/ioctl/dqbuf.rs
Normal file
66
libs/v4l2r/src/ioctl/dqbuf.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::QueueType;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DqBufIoctlError {
|
||||
#[error("end-of-stream reached")]
|
||||
Eos,
|
||||
#[error("no buffer ready for dequeue")]
|
||||
NotReady,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for DqBufIoctlError {
|
||||
fn from(error: Errno) -> Self {
|
||||
match error {
|
||||
Errno::EAGAIN => Self::NotReady,
|
||||
Errno::EPIPE => Self::Eos,
|
||||
error => Self::Other(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DqBufIoctlError> for Errno {
|
||||
fn from(err: DqBufIoctlError) -> Self {
|
||||
match err {
|
||||
DqBufIoctlError::Eos => Errno::EPIPE,
|
||||
DqBufIoctlError::NotReady => Errno::EAGAIN,
|
||||
DqBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DqBufError<CE> = IoctlConvertError<DqBufIoctlError, CE>;
|
||||
pub type DqBufResult<O, CE> = IoctlConvertResult<O, DqBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_DQBUF` ioctl.
|
||||
pub fn dqbuf<O>(fd: &impl AsRawFd, queue: QueueType) -> DqBufResult<O, O::Error>
|
||||
where
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, None);
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_dqbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
137
libs/v4l2r/src/ioctl/enum_fmt.rs
Normal file
137
libs/v4l2r/src/ioctl/enum_fmt.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Safe wrapper for the `VIDIOC_ENUM_FMT` ioctl.
|
||||
use super::string_from_cstr;
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_fmtdesc;
|
||||
use crate::{PixelFormat, QueueType};
|
||||
use bitflags::bitflags;
|
||||
use log::error;
|
||||
use nix::errno::Errno;
|
||||
use std::fmt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_ENUM_FMT` ioctl into the `flags` field of
|
||||
/// `struct v4l2_fmtdesc`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FormatFlags: u32 {
|
||||
const COMPRESSED = bindings::V4L2_FMT_FLAG_COMPRESSED;
|
||||
const EMULATED = bindings::V4L2_FMT_FLAG_EMULATED;
|
||||
}
|
||||
}
|
||||
/// Quickly get the Fourcc code of a format.
|
||||
impl From<v4l2_fmtdesc> for PixelFormat {
|
||||
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
|
||||
fmtdesc.pixelformat.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe variant of the `v4l2_fmtdesc` struct, to be used with `enum_fmt`.
|
||||
#[derive(Debug)]
|
||||
pub struct FmtDesc {
|
||||
pub flags: FormatFlags,
|
||||
pub description: String,
|
||||
pub pixelformat: PixelFormat,
|
||||
}
|
||||
|
||||
impl fmt::Display for FmtDesc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {} {}",
|
||||
self.pixelformat,
|
||||
self.description,
|
||||
if self.flags.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!("({:?})", self.flags)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_fmtdesc> for FmtDesc {
|
||||
fn from(fmtdesc: v4l2_fmtdesc) -> Self {
|
||||
FmtDesc {
|
||||
flags: FormatFlags::from_bits_truncate(fmtdesc.flags),
|
||||
description: string_from_cstr(&fmtdesc.description).unwrap_or_else(|_| "".into()),
|
||||
pixelformat: fmtdesc.pixelformat.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_fmtdesc;
|
||||
nix::ioctl_readwrite!(vidioc_enum_fmt, b'V', 2, v4l2_fmtdesc);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumFmtError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] nix::Error),
|
||||
}
|
||||
|
||||
impl From<EnumFmtError> for Errno {
|
||||
fn from(err: EnumFmtError) -> Self {
|
||||
match err {
|
||||
EnumFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FMT` ioctl.
|
||||
pub fn enum_fmt<T: From<v4l2_fmtdesc>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
index: u32,
|
||||
) -> Result<T, EnumFmtError> {
|
||||
let mut fmtdesc = v4l2_fmtdesc {
|
||||
type_: queue as u32,
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
unsafe { ioctl::vidioc_enum_fmt(fd.as_raw_fd(), &mut fmtdesc) }?;
|
||||
|
||||
Ok(T::from(fmtdesc))
|
||||
}
|
||||
|
||||
/// Iterator over the formats of the given queue. This takes a reference to the
|
||||
/// device's file descriptor so no operation that could affect the format
|
||||
/// enumeration can take place while the iterator exists.
|
||||
pub struct FormatIterator<'a, F: AsRawFd> {
|
||||
fd: &'a F,
|
||||
queue: QueueType,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl<'a, F: AsRawFd> FormatIterator<'a, F> {
|
||||
/// Create a new iterator listing all the currently valid formats on
|
||||
/// `queue`.
|
||||
pub fn new(fd: &'a F, queue: QueueType) -> Self {
|
||||
FormatIterator {
|
||||
fd,
|
||||
queue,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: AsRawFd> Iterator for FormatIterator<'a, F> {
|
||||
type Item = FmtDesc;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match enum_fmt(self.fd, self.queue, self.index) {
|
||||
Ok(fmtdesc) => {
|
||||
self.index += 1;
|
||||
Some(fmtdesc)
|
||||
}
|
||||
// EINVAL means we have reached the last format.
|
||||
Err(EnumFmtError::IoctlError(Errno::EINVAL)) => None,
|
||||
_ => {
|
||||
error!("Unexpected return value for VIDIOC_ENUM_FMT!");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
libs/v4l2r/src/ioctl/expbuf.rs
Normal file
61
libs/v4l2r/src/ioctl/expbuf.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
//! Safe wrapper for the `VIDIOC_EXPBUF` ioctl.
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::OFlag;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings::v4l2_exportbuffer;
|
||||
use crate::QueueType;
|
||||
|
||||
bitflags! {
|
||||
/// Flags that can be passed when exporting the buffer.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ExpbufFlags: u32 {
|
||||
const CLOEXEC = OFlag::O_CLOEXEC.bits() as u32;
|
||||
const RDONLY = OFlag::O_RDONLY.bits() as u32;
|
||||
const WRONLY = OFlag::O_WRONLY.bits() as u32;
|
||||
const RDWR = OFlag::O_RDWR.bits() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_exportbuffer;
|
||||
nix::ioctl_readwrite!(vidioc_expbuf, b'V', 16, v4l2_exportbuffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExpbufError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<ExpbufError> for Errno {
|
||||
fn from(err: ExpbufError) -> Self {
|
||||
match err {
|
||||
ExpbufError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_EXPBUF` ioctl.
|
||||
pub fn expbuf<R: FromRawFd>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
index: usize,
|
||||
plane: usize,
|
||||
flags: ExpbufFlags,
|
||||
) -> Result<R, ExpbufError> {
|
||||
let mut v4l2_expbuf = v4l2_exportbuffer {
|
||||
type_: queue as u32,
|
||||
index: index as u32,
|
||||
plane: plane as u32,
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe { ioctl::vidioc_expbuf(fd.as_raw_fd(), &mut v4l2_expbuf) }?;
|
||||
|
||||
Ok(unsafe { R::from_raw_fd(v4l2_expbuf.fd) })
|
||||
}
|
||||
82
libs/v4l2r/src/ioctl/frameintervals.rs
Normal file
82
libs/v4l2r/src/ioctl/frameintervals.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_frmivalenum;
|
||||
use crate::PixelFormat;
|
||||
|
||||
/// A wrapper for the 'v4l2_frmivalenum' union member types
|
||||
#[derive(Debug)]
|
||||
pub enum FrmIvalTypes<'a> {
|
||||
Discrete(&'a bindings::v4l2_fract),
|
||||
StepWise(&'a bindings::v4l2_frmival_stepwise),
|
||||
}
|
||||
|
||||
impl v4l2_frmivalenum {
|
||||
/// Safely access the intervals member of the struct based on the
|
||||
/// returned type.
|
||||
pub fn intervals(&self) -> Option<FrmIvalTypes<'_>> {
|
||||
match self.type_ {
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_DISCRETE => {
|
||||
Some(FrmIvalTypes::Discrete(unsafe {
|
||||
&self.__bindgen_anon_1.discrete
|
||||
}))
|
||||
}
|
||||
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_CONTINUOUS
|
||||
| bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_STEPWISE => {
|
||||
Some(FrmIvalTypes::StepWise(unsafe {
|
||||
&self.__bindgen_anon_1.stepwise
|
||||
}))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_frmivalenum;
|
||||
nix::ioctl_readwrite!(vidioc_enum_frameintervals, b'V', 75, v4l2_frmivalenum);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FrameIntervalsError {
|
||||
#[error("Unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<FrameIntervalsError> for Errno {
|
||||
fn from(err: FrameIntervalsError) -> Self {
|
||||
match err {
|
||||
FrameIntervalsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FRAMEINTERVALS` ioctl.
|
||||
pub fn enum_frame_intervals<O: From<v4l2_frmivalenum>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
pixel_format: PixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<O, FrameIntervalsError> {
|
||||
let mut frame_interval = v4l2_frmivalenum {
|
||||
index,
|
||||
pixel_format: pixel_format.into(),
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_frameintervals(fd.as_raw_fd(), &mut frame_interval) } {
|
||||
Ok(_) => Ok(O::from(frame_interval)),
|
||||
Err(e) => Err(FrameIntervalsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
79
libs/v4l2r/src/ioctl/framesizes.rs
Normal file
79
libs/v4l2r/src/ioctl/framesizes.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_frmsizeenum;
|
||||
use crate::PixelFormat;
|
||||
|
||||
/// A wrapper for the 'v4l2_frmsizeenum' union member types
|
||||
#[derive(Debug)]
|
||||
pub enum FrmSizeTypes<'a> {
|
||||
Discrete(&'a bindings::v4l2_frmsize_discrete),
|
||||
StepWise(&'a bindings::v4l2_frmsize_stepwise),
|
||||
}
|
||||
|
||||
impl v4l2_frmsizeenum {
|
||||
/// Safely access the size member of the struct based on the
|
||||
/// returned type.
|
||||
pub fn size(&self) -> Option<FrmSizeTypes<'_>> {
|
||||
match self.type_ {
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE => {
|
||||
Some(FrmSizeTypes::Discrete(unsafe {
|
||||
&self.__bindgen_anon_1.discrete
|
||||
}))
|
||||
}
|
||||
|
||||
// SAFETY: the member of the union that gets used by the driver
|
||||
// is determined by the type
|
||||
bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_CONTINUOUS
|
||||
| bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_STEPWISE => {
|
||||
Some(FrmSizeTypes::StepWise(unsafe {
|
||||
&self.__bindgen_anon_1.stepwise
|
||||
}))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_frmsizeenum;
|
||||
nix::ioctl_readwrite!(vidioc_enum_framesizes, b'V', 74, v4l2_frmsizeenum);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FrameSizeError {
|
||||
#[error("Unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<FrameSizeError> for Errno {
|
||||
fn from(err: FrameSizeError) -> Self {
|
||||
match err {
|
||||
FrameSizeError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_FRAMESIZES` ioctl.
|
||||
pub fn enum_frame_sizes<O: From<v4l2_frmsizeenum>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
pixel_format: PixelFormat,
|
||||
) -> Result<O, FrameSizeError> {
|
||||
let mut frame_size = v4l2_frmsizeenum {
|
||||
index,
|
||||
pixel_format: pixel_format.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_framesizes(fd.as_raw_fd(), &mut frame_size) } {
|
||||
Ok(_) => Ok(O::from(frame_size)),
|
||||
Err(e) => Err(FrameSizeError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
189
libs/v4l2r/src/ioctl/g_dv_timings.rs
Normal file
189
libs/v4l2r/src/ioctl/g_dv_timings.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use enumn::N;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_dv_timings;
|
||||
use crate::bindings::v4l2_dv_timings_cap;
|
||||
use crate::bindings::v4l2_enum_dv_timings;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_dv_timings;
|
||||
use crate::bindings::v4l2_dv_timings_cap;
|
||||
use crate::bindings::v4l2_enum_dv_timings;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_s_dv_timings, b'V', 87, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_g_dv_timings, b'V', 88, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_enum_dv_timings, b'V', 98, v4l2_enum_dv_timings);
|
||||
nix::ioctl_read!(vidioc_query_dv_timings, b'V', 99, v4l2_dv_timings);
|
||||
nix::ioctl_readwrite!(vidioc_dv_timings_cap, b'V', 100, v4l2_dv_timings_cap);
|
||||
}
|
||||
|
||||
#[derive(Debug, N)]
|
||||
#[repr(u32)]
|
||||
pub enum DvTimingsType {
|
||||
Bt6561120 = bindings::V4L2_DV_BT_656_1120,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GDvTimingsError {
|
||||
#[error("ioctl not supported or invalid parameters")]
|
||||
Invalid,
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("Device is busy and cannot change timings")]
|
||||
Busy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GDvTimingsError> for Errno {
|
||||
fn from(err: GDvTimingsError) -> Self {
|
||||
match err {
|
||||
GDvTimingsError::Invalid => Errno::EINVAL,
|
||||
GDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
GDvTimingsError::Busy => Errno::EBUSY,
|
||||
GDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_DV_TIMINGS` ioctl.
|
||||
pub fn s_dv_timings<I: Into<v4l2_dv_timings>, O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
timings: I,
|
||||
) -> Result<O, GDvTimingsError> {
|
||||
let mut timings: v4l2_dv_timings = timings.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
|
||||
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
|
||||
Err(e) => Err(GDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_DV_TIMINGS` ioctl.
|
||||
pub fn g_dv_timings<O: From<v4l2_dv_timings>>(fd: &impl AsRawFd) -> Result<O, GDvTimingsError> {
|
||||
let mut timings = v4l2_dv_timings {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::EINVAL) => Err(GDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(GDvTimingsError::Unsupported),
|
||||
Err(Errno::EBUSY) => Err(GDvTimingsError::Busy),
|
||||
Err(e) => Err(GDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumDvTimingsError {
|
||||
#[error("timing index is out of bounds")]
|
||||
Invalid,
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<EnumDvTimingsError> for Errno {
|
||||
fn from(err: EnumDvTimingsError) -> Self {
|
||||
match err {
|
||||
EnumDvTimingsError::Invalid => Errno::EINVAL,
|
||||
EnumDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
EnumDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUM_DV_TIMINGS` ioctl.
|
||||
pub fn enum_dv_timings<O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
index: u32,
|
||||
) -> Result<O, EnumDvTimingsError> {
|
||||
let mut timings = v4l2_enum_dv_timings {
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enum_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings.timings)),
|
||||
Err(Errno::EINVAL) => Err(EnumDvTimingsError::Invalid),
|
||||
Err(Errno::ENODATA) => Err(EnumDvTimingsError::Unsupported),
|
||||
Err(e) => Err(EnumDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryDvTimingsError {
|
||||
#[error("Digital video timings are not supported on this input or output")]
|
||||
Unsupported,
|
||||
#[error("No timings could be detected because no signal was found")]
|
||||
NoLink,
|
||||
#[error("Unstable signal")]
|
||||
UnstableSignal,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<QueryDvTimingsError> for Errno {
|
||||
fn from(err: QueryDvTimingsError) -> Self {
|
||||
match err {
|
||||
QueryDvTimingsError::Unsupported => Errno::ENODATA,
|
||||
QueryDvTimingsError::NoLink => Errno::ENOLINK,
|
||||
QueryDvTimingsError::UnstableSignal => Errno::ENOLCK,
|
||||
QueryDvTimingsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERY_DV_TIMINGS` ioctl.
|
||||
pub fn query_dv_timings<O: From<v4l2_dv_timings>>(
|
||||
fd: &impl AsRawFd,
|
||||
) -> Result<O, QueryDvTimingsError> {
|
||||
let mut timings = v4l2_dv_timings {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_query_dv_timings(fd.as_raw_fd(), &mut timings) } {
|
||||
Ok(_) => Ok(O::from(timings)),
|
||||
Err(Errno::ENODATA) => Err(QueryDvTimingsError::Unsupported),
|
||||
Err(Errno::ENOLINK) => Err(QueryDvTimingsError::NoLink),
|
||||
Err(Errno::ENOLCK) => Err(QueryDvTimingsError::UnstableSignal),
|
||||
Err(e) => Err(QueryDvTimingsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DvTimingsCapError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<DvTimingsCapError> for Errno {
|
||||
fn from(err: DvTimingsCapError) -> Self {
|
||||
match err {
|
||||
DvTimingsCapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_DV_TIMINGS_CAP` ioctl.
|
||||
pub fn dv_timings_cap<O: From<v4l2_dv_timings_cap>>(
|
||||
fd: &impl AsRawFd,
|
||||
) -> Result<O, DvTimingsCapError> {
|
||||
let mut caps = v4l2_dv_timings_cap {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_dv_timings_cap(fd.as_raw_fd(), &mut caps) } {
|
||||
Ok(_) => Ok(O::from(caps)),
|
||||
Err(e) => Err(DvTimingsCapError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
300
libs/v4l2r/src/ioctl/g_fmt.rs
Normal file
300
libs/v4l2r/src/ioctl/g_fmt.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
//! Safe wrapper for the `VIDIOC_(G|S|TRY)_FMT` ioctls.
|
||||
use nix::errno::Errno;
|
||||
use std::convert::{From, Into, TryFrom, TryInto};
|
||||
use std::default::Default;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_format;
|
||||
use crate::Format;
|
||||
use crate::FormatConversionError;
|
||||
use crate::PlaneLayout;
|
||||
use crate::QueueType;
|
||||
|
||||
impl TryFrom<(QueueType, &Format)> for v4l2_format {
|
||||
type Error = FormatConversionError;
|
||||
|
||||
fn try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error> {
|
||||
Ok(v4l2_format {
|
||||
type_: queue as u32,
|
||||
fmt: match queue {
|
||||
QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => {
|
||||
bindings::v4l2_format__bindgen_ty_1 {
|
||||
pix_mp: {
|
||||
if format.plane_fmt.len() > bindings::VIDEO_MAX_PLANES as usize {
|
||||
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
|
||||
}
|
||||
|
||||
let mut pix_mp = bindings::v4l2_pix_format_mplane {
|
||||
width: format.width,
|
||||
height: format.height,
|
||||
pixelformat: format.pixelformat.into(),
|
||||
num_planes: format.plane_fmt.len() as u8,
|
||||
plane_fmt: Default::default(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for (plane, v4l2_plane) in
|
||||
format.plane_fmt.iter().zip(pix_mp.plane_fmt.iter_mut())
|
||||
{
|
||||
*v4l2_plane = plane.into();
|
||||
}
|
||||
|
||||
pix_mp
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => bindings::v4l2_format__bindgen_ty_1 {
|
||||
pix: {
|
||||
if format.plane_fmt.len() > 1 {
|
||||
return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
|
||||
}
|
||||
|
||||
let (bytesperline, sizeimage) = if !format.plane_fmt.is_empty() {
|
||||
(
|
||||
format.plane_fmt[0].bytesperline,
|
||||
format.plane_fmt[0].sizeimage,
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
bindings::v4l2_pix_format {
|
||||
width: format.width,
|
||||
height: format.height,
|
||||
pixelformat: format.pixelformat.into(),
|
||||
bytesperline,
|
||||
sizeimage,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PlaneLayout> for bindings::v4l2_plane_pix_format {
|
||||
fn from(plane: &PlaneLayout) -> Self {
|
||||
bindings::v4l2_plane_pix_format {
|
||||
sizeimage: plane.sizeimage,
|
||||
bytesperline: plane.bytesperline,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_format;
|
||||
nix::ioctl_readwrite!(vidioc_g_fmt, b'V', 4, v4l2_format);
|
||||
nix::ioctl_readwrite!(vidioc_s_fmt, b'V', 5, v4l2_format);
|
||||
nix::ioctl_readwrite!(vidioc_try_fmt, b'V', 64, v4l2_format);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<GFmtError> for Errno {
|
||||
fn from(err: GFmtError) -> Self {
|
||||
match err {
|
||||
GFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
GFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
GFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_FMT` ioctl.
|
||||
pub fn g_fmt<O: TryFrom<v4l2_format>>(fd: &impl AsRawFd, queue: QueueType) -> Result<O, GFmtError> {
|
||||
let mut fmt = v4l2_format {
|
||||
type_: queue as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| GFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(GFmtError::InvalidBufferType),
|
||||
Err(e) => Err(GFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("error while converting to V4L2 format")]
|
||||
ToV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("device currently busy")]
|
||||
DeviceBusy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<SFmtError> for Errno {
|
||||
fn from(err: SFmtError) -> Self {
|
||||
match err {
|
||||
SFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
SFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
|
||||
SFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
SFmtError::DeviceBusy => Errno::EBUSY,
|
||||
SFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_FMT` ioctl.
|
||||
pub fn s_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
|
||||
fd: &mut impl AsRawFd,
|
||||
format: I,
|
||||
) -> Result<O, SFmtError> {
|
||||
let mut fmt: v4l2_format = format
|
||||
.try_into()
|
||||
.map_err(|_| SFmtError::ToV4L2FormatConversionError)?;
|
||||
|
||||
match unsafe { ioctl::vidioc_s_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| SFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(SFmtError::InvalidBufferType),
|
||||
Err(Errno::EBUSY) => Err(SFmtError::DeviceBusy),
|
||||
Err(e) => Err(SFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TryFmtError {
|
||||
#[error("error while converting from V4L2 format")]
|
||||
FromV4L2FormatConversionError,
|
||||
#[error("error while converting to V4L2 format")]
|
||||
ToV4L2FormatConversionError,
|
||||
#[error("invalid buffer type requested")]
|
||||
InvalidBufferType,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<TryFmtError> for Errno {
|
||||
fn from(err: TryFmtError) -> Self {
|
||||
match err {
|
||||
TryFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
|
||||
TryFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
|
||||
TryFmtError::InvalidBufferType => Errno::EINVAL,
|
||||
TryFmtError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_TRY_FMT` ioctl.
|
||||
pub fn try_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
|
||||
fd: &impl AsRawFd,
|
||||
format: I,
|
||||
) -> Result<O, TryFmtError> {
|
||||
let mut fmt: v4l2_format = format
|
||||
.try_into()
|
||||
.map_err(|_| TryFmtError::ToV4L2FormatConversionError)?;
|
||||
|
||||
match unsafe { ioctl::vidioc_try_fmt(fd.as_raw_fd(), &mut fmt) } {
|
||||
Ok(_) => Ok(fmt
|
||||
.try_into()
|
||||
.map_err(|_| TryFmtError::FromV4L2FormatConversionError)?),
|
||||
Err(Errno::EINVAL) => Err(TryFmtError::InvalidBufferType),
|
||||
Err(e) => Err(TryFmtError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
// Convert from Format to multi-planar v4l2_format and back.
|
||||
fn mplane_to_v4l2_format() {
|
||||
// This is not a real format but let us use unique values per field.
|
||||
let mplane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NM12".into(),
|
||||
plane_fmt: vec![
|
||||
PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 153600,
|
||||
bytesperline: 320,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 76800,
|
||||
bytesperline: 160,
|
||||
},
|
||||
],
|
||||
};
|
||||
let v4l2_format = v4l2_format {
|
||||
..(QueueType::VideoCaptureMplane, &mplane).try_into().unwrap()
|
||||
};
|
||||
let mplane2: Format = v4l2_format.try_into().unwrap();
|
||||
assert_eq!(mplane, mplane2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Convert from Format to single-planar v4l2_format and back.
|
||||
fn splane_to_v4l2_format() {
|
||||
// This is not a real format but let us use unique values per field.
|
||||
let splane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NV12".into(),
|
||||
plane_fmt: vec![PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
}],
|
||||
};
|
||||
// Conversion to/from single-planar format.
|
||||
let v4l2_format = v4l2_format {
|
||||
..(QueueType::VideoCapture, &splane).try_into().unwrap()
|
||||
};
|
||||
let splane2: Format = v4l2_format.try_into().unwrap();
|
||||
assert_eq!(splane, splane2);
|
||||
|
||||
// Trying to use a multi-planar format with the single-planar API should
|
||||
// fail.
|
||||
let mplane = Format {
|
||||
width: 632,
|
||||
height: 480,
|
||||
pixelformat: b"NM12".into(),
|
||||
// This is not a real format but let us use unique values per field.
|
||||
plane_fmt: vec![
|
||||
PlaneLayout {
|
||||
sizeimage: 307200,
|
||||
bytesperline: 640,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 153600,
|
||||
bytesperline: 320,
|
||||
},
|
||||
PlaneLayout {
|
||||
sizeimage: 76800,
|
||||
bytesperline: 160,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
TryInto::<v4l2_format>::try_into((QueueType::VideoCapture, &mplane)).err(),
|
||||
Some(FormatConversionError::TooManyPlanes(3))
|
||||
);
|
||||
}
|
||||
}
|
||||
149
libs/v4l2r/src/ioctl/g_parm.rs
Normal file
149
libs/v4l2r/src/ioctl/g_parm.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings::v4l2_standard;
|
||||
use crate::bindings::v4l2_std_id;
|
||||
use crate::bindings::v4l2_streamparm;
|
||||
use crate::QueueType;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_standard;
|
||||
use crate::bindings::v4l2_std_id;
|
||||
use crate::bindings::v4l2_streamparm;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_g_parm, b'V', 21, v4l2_streamparm);
|
||||
nix::ioctl_readwrite!(vidioc_s_parm, b'V', 22, v4l2_streamparm);
|
||||
nix::ioctl_read!(vidioc_g_std, b'V', 23, v4l2_std_id);
|
||||
nix::ioctl_write_ptr!(vidioc_s_std, b'V', 24, v4l2_std_id);
|
||||
nix::ioctl_readwrite!(vidioc_enumstd, b'V', 25, v4l2_standard);
|
||||
nix::ioctl_read!(vidioc_querystd, b'V', 63, v4l2_std_id);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GParmError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GParmError> for Errno {
|
||||
fn from(err: GParmError) -> Self {
|
||||
match err {
|
||||
GParmError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_PARM` ioctl.
|
||||
pub fn g_parm<O: From<v4l2_streamparm>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
) -> Result<O, GParmError> {
|
||||
let mut parm = v4l2_streamparm {
|
||||
type_: queue as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_parm(fd.as_raw_fd(), &mut parm) } {
|
||||
Ok(_) => Ok(O::from(parm)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_PARM` ioctl.
|
||||
pub fn s_parm<I: Into<v4l2_streamparm>, O: From<v4l2_streamparm>>(
|
||||
fd: &impl AsRawFd,
|
||||
parm: I,
|
||||
) -> Result<O, GParmError> {
|
||||
let mut parm = parm.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_parm(fd.as_raw_fd(), &mut parm) } {
|
||||
Ok(_) => Ok(O::from(parm)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_STD` ioctl.
|
||||
pub fn g_std<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
|
||||
let mut std_id: v4l2_std_id = 0;
|
||||
|
||||
match unsafe { ioctl::vidioc_g_std(fd.as_raw_fd(), &mut std_id) } {
|
||||
Ok(_) => Ok(O::from(std_id)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SStdError {
|
||||
#[error("unsupported standard requested")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<SStdError> for Errno {
|
||||
fn from(err: SStdError) -> Self {
|
||||
match err {
|
||||
SStdError::Unsupported => Errno::EINVAL,
|
||||
SStdError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_STD` ioctl.
|
||||
pub fn s_std<I: Into<v4l2_std_id>>(fd: &impl AsRawFd, std_id: I) -> Result<(), SStdError> {
|
||||
let std_id = std_id.into();
|
||||
|
||||
match unsafe { ioctl::vidioc_s_std(fd.as_raw_fd(), &std_id) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(SStdError::Unsupported),
|
||||
Err(e) => Err(SStdError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EnumStdError {
|
||||
#[error("requested index is out of bounds")]
|
||||
OutOfBounds,
|
||||
#[error("standard video timings are not supported for this input or output")]
|
||||
Unsupported,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<EnumStdError> for Errno {
|
||||
fn from(err: EnumStdError) -> Self {
|
||||
match err {
|
||||
EnumStdError::OutOfBounds => Errno::EINVAL,
|
||||
EnumStdError::Unsupported => Errno::ENODATA,
|
||||
EnumStdError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_ENUMSTD` ioctl.
|
||||
pub fn enumstd<O: From<v4l2_standard>>(fd: &impl AsRawFd, index: u32) -> Result<O, EnumStdError> {
|
||||
let mut standard = v4l2_standard {
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_enumstd(fd.as_raw_fd(), &mut standard) } {
|
||||
Ok(_) => Ok(O::from(standard)),
|
||||
Err(Errno::EINVAL) => Err(EnumStdError::OutOfBounds),
|
||||
Err(Errno::ENODATA) => Err(EnumStdError::Unsupported),
|
||||
Err(e) => Err(EnumStdError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYSTD` ioctl.
|
||||
pub fn querystd<O: From<v4l2_std_id>>(fd: &impl AsRawFd) -> Result<O, GParmError> {
|
||||
let mut std_id: v4l2_std_id = 0;
|
||||
|
||||
match unsafe { ioctl::vidioc_querystd(fd.as_raw_fd(), &mut std_id) } {
|
||||
Ok(_) => Ok(O::from(std_id)),
|
||||
Err(e) => Err(GParmError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
130
libs/v4l2r/src/ioctl/g_selection.rs
Normal file
130
libs/v4l2r/src/ioctl/g_selection.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use enumn::N;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_rect;
|
||||
use crate::bindings::v4l2_selection;
|
||||
|
||||
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum SelectionType {
|
||||
Capture = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
Output = bindings::v4l2_buf_type_V4L2_BUF_TYPE_VIDEO_OUTPUT,
|
||||
}
|
||||
|
||||
#[derive(Debug, N, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum SelectionTarget {
|
||||
Crop = bindings::V4L2_SEL_TGT_CROP,
|
||||
CropDefault = bindings::V4L2_SEL_TGT_CROP_DEFAULT,
|
||||
CropBounds = bindings::V4L2_SEL_TGT_CROP_BOUNDS,
|
||||
NativeSize = bindings::V4L2_SEL_TGT_NATIVE_SIZE,
|
||||
Compose = bindings::V4L2_SEL_TGT_COMPOSE,
|
||||
ComposeDefault = bindings::V4L2_SEL_TGT_COMPOSE_DEFAULT,
|
||||
ComposeBounds = bindings::V4L2_SEL_TGT_COMPOSE_BOUNDS,
|
||||
ComposePadded = bindings::V4L2_SEL_TGT_COMPOSE_PADDED,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SelectionFlags: u32 {
|
||||
const GE = bindings::V4L2_SEL_FLAG_GE;
|
||||
const LE = bindings::V4L2_SEL_FLAG_LE;
|
||||
const KEEP_CONFIG = bindings::V4L2_SEL_FLAG_KEEP_CONFIG;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_selection;
|
||||
nix::ioctl_readwrite!(vidioc_g_selection, b'V', 94, v4l2_selection);
|
||||
nix::ioctl_readwrite!(vidioc_s_selection, b'V', 95, v4l2_selection);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GSelectionError {
|
||||
#[error("invalid type or target requested")]
|
||||
Invalid,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<GSelectionError> for Errno {
|
||||
fn from(err: GSelectionError) -> Self {
|
||||
match err {
|
||||
GSelectionError::Invalid => Errno::EINVAL,
|
||||
GSelectionError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_G_SELECTION` ioctl.
|
||||
pub fn g_selection<R: From<v4l2_rect>>(
|
||||
fd: &impl AsRawFd,
|
||||
selection: SelectionType,
|
||||
target: SelectionTarget,
|
||||
) -> Result<R, GSelectionError> {
|
||||
let mut sel = v4l2_selection {
|
||||
type_: selection as u32,
|
||||
target: target as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_g_selection(fd.as_raw_fd(), &mut sel) } {
|
||||
Ok(_) => Ok(R::from(sel.r)),
|
||||
Err(Errno::EINVAL) => Err(GSelectionError::Invalid),
|
||||
Err(e) => Err(GSelectionError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SSelectionError {
|
||||
#[error("invalid type or target requested")]
|
||||
Invalid,
|
||||
#[error("invalid range requested")]
|
||||
InvalidRange,
|
||||
#[error("cannot change selection rectangle currently")]
|
||||
Busy,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<SSelectionError> for Errno {
|
||||
fn from(err: SSelectionError) -> Self {
|
||||
match err {
|
||||
SSelectionError::Invalid => Errno::EINVAL,
|
||||
SSelectionError::InvalidRange => Errno::ERANGE,
|
||||
SSelectionError::Busy => Errno::EBUSY,
|
||||
SSelectionError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_S_SELECTION` ioctl.
|
||||
pub fn s_selection<RI: Into<v4l2_rect>, RO: From<v4l2_rect>>(
|
||||
fd: &impl AsRawFd,
|
||||
selection: SelectionType,
|
||||
target: SelectionTarget,
|
||||
rect: RI,
|
||||
flags: SelectionFlags,
|
||||
) -> Result<RO, SSelectionError> {
|
||||
let mut sel = v4l2_selection {
|
||||
type_: selection as u32,
|
||||
target: target as u32,
|
||||
flags: flags.bits(),
|
||||
r: rect.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_s_selection(fd.as_raw_fd(), &mut sel) } {
|
||||
Ok(_) => Ok(RO::from(sel.r)),
|
||||
Err(Errno::EINVAL) => Err(SSelectionError::Invalid),
|
||||
Err(Errno::ERANGE) => Err(SSelectionError::InvalidRange),
|
||||
Err(Errno::EBUSY) => Err(SSelectionError::Busy),
|
||||
Err(e) => Err(SSelectionError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
117
libs/v4l2r/src/ioctl/mmap.rs
Normal file
117
libs/v4l2r/src/ioctl/mmap.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use core::num::NonZeroUsize;
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
ops::Deref,
|
||||
ptr::NonNull,
|
||||
slice,
|
||||
};
|
||||
use std::{ops::DerefMut, os::unix::io::AsFd};
|
||||
|
||||
use log::error;
|
||||
use nix::{errno::Errno, libc::off_t, sys::mman};
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct PlaneMapping {
|
||||
// A mapping remains valid until we munmap it, that is, until the
|
||||
// PlaneMapping object is deleted. Hence the static lifetime.
|
||||
pub data: &'static mut [u8],
|
||||
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl PlaneMapping {
|
||||
pub fn size(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
|
||||
pub fn restrict(mut self, start: usize, end: usize) -> Self {
|
||||
self.start = max(self.start, start);
|
||||
self.end = min(self.end, end);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PlaneMapping {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for PlaneMapping {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PlaneMapping {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for PlaneMapping {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data[self.start..self.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PlaneMapping {
|
||||
fn drop(&mut self) {
|
||||
// Safe because the pointer and length were constructed in mmap() and
|
||||
// are always valid.
|
||||
unsafe {
|
||||
mman::munmap(
|
||||
NonNull::new_unchecked(self.data.as_mut_ptr().cast()),
|
||||
self.data.len(),
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Error while unmapping plane: {}", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MmapError {
|
||||
#[error("provided length was 0")]
|
||||
ZeroLength,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<MmapError> for Errno {
|
||||
fn from(err: MmapError) -> Self {
|
||||
match err {
|
||||
MmapError::ZeroLength => Errno::EINVAL,
|
||||
MmapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO should be unsafe because the mapping can be used after a buffer is queued?
|
||||
// Or not, since this cannot cause a crash...
|
||||
pub fn mmap(fd: &impl AsFd, mem_offset: u32, length: u32) -> Result<PlaneMapping, MmapError> {
|
||||
let non_zero_length = NonZeroUsize::new(length as usize).ok_or(MmapError::ZeroLength)?;
|
||||
let data = unsafe {
|
||||
mman::mmap(
|
||||
None,
|
||||
non_zero_length,
|
||||
mman::ProtFlags::PROT_READ | mman::ProtFlags::PROT_WRITE,
|
||||
mman::MapFlags::MAP_SHARED,
|
||||
fd,
|
||||
mem_offset as off_t,
|
||||
)
|
||||
}?;
|
||||
|
||||
Ok(PlaneMapping {
|
||||
// Safe because we know the pointer is valid and has enough data mapped
|
||||
// to cover the length.
|
||||
data: unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), length as usize) },
|
||||
start: 0,
|
||||
end: length as usize,
|
||||
})
|
||||
}
|
||||
197
libs/v4l2r/src/ioctl/qbuf.rs
Normal file
197
libs/v4l2r/src/ioctl/qbuf.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Safe wrapper for the VIDIOC_(D)QBUF and VIDIOC_QUERYBUF ioctls.
|
||||
use nix::errno::Errno;
|
||||
use nix::libc::{suseconds_t, time_t};
|
||||
use nix::sys::time::{TimeVal, TimeValLike};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::BufferFlags;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::memory::Memory;
|
||||
use crate::memory::PlaneHandle;
|
||||
use crate::QueueType;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QBufIoctlError {
|
||||
#[error("invalid number of planes specified for the buffer: got {0}, expected {1}")]
|
||||
NumPlanesMismatch(usize, usize),
|
||||
#[error("data offset specified while using the single-planar API")]
|
||||
DataOffsetNotSupported,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for QBufIoctlError {
|
||||
fn from(errno: Errno) -> Self {
|
||||
Self::Other(errno)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QBufIoctlError> for Errno {
|
||||
fn from(err: QBufIoctlError) -> Self {
|
||||
match err {
|
||||
QBufIoctlError::NumPlanesMismatch(_, _) => Errno::EINVAL,
|
||||
QBufIoctlError::DataOffsetNotSupported => Errno::EINVAL,
|
||||
QBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a single plane of a V4L2 buffer.
|
||||
pub struct QBufPlane(pub bindings::v4l2_plane);
|
||||
|
||||
impl QBufPlane {
|
||||
// TODO remove as this is not safe - we should always specify a handle.
|
||||
pub fn new(bytes_used: usize) -> Self {
|
||||
QBufPlane(bindings::v4l2_plane {
|
||||
bytesused: bytes_used as u32,
|
||||
data_offset: 0,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_from_handle<H: PlaneHandle>(handle: &H, bytes_used: usize) -> Self {
|
||||
let mut plane = Self::new(bytes_used);
|
||||
handle.fill_v4l2_plane(&mut plane.0);
|
||||
plane
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for QBufPlane {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("QBufPlane")
|
||||
.field("bytesused", &self.0.bytesused)
|
||||
.field("data_offset", &self.0.data_offset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all the information that can be passed to the `qbuf` ioctl.
|
||||
// TODO Change this to contain a v4l2_buffer, and create constructors/methods
|
||||
// to change it? Then during qbuf we just need to set m.planes to planes
|
||||
// (after resizing it to 8) and we are good to use it as-is.
|
||||
// We could even turn the trait into AsRef<v4l2_buffer> for good measure.
|
||||
#[derive(Debug)]
|
||||
pub struct QBuffer<H: PlaneHandle> {
|
||||
index: u32,
|
||||
queue: QueueType,
|
||||
pub flags: BufferFlags,
|
||||
pub field: u32,
|
||||
pub sequence: u32,
|
||||
pub timestamp: TimeVal,
|
||||
pub planes: Vec<QBufPlane>,
|
||||
pub _h: std::marker::PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> QBuffer<H> {
|
||||
pub fn new(queue: QueueType, index: u32) -> Self {
|
||||
QBuffer {
|
||||
index,
|
||||
queue,
|
||||
flags: Default::default(),
|
||||
field: Default::default(),
|
||||
sequence: Default::default(),
|
||||
timestamp: TimeVal::zero(),
|
||||
planes: Vec::new(),
|
||||
_h: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> QBuffer<H> {
|
||||
pub fn set_timestamp(mut self, sec: time_t, usec: suseconds_t) -> Self {
|
||||
self.timestamp = TimeVal::new(sec, usec);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: PlaneHandle> From<QBuffer<H>> for UncheckedV4l2Buffer {
|
||||
fn from(qbuf: QBuffer<H>) -> Self {
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(qbuf.queue, Some(qbuf.index));
|
||||
v4l2_buf.0.index = qbuf.index;
|
||||
v4l2_buf.0.type_ = qbuf.queue as u32;
|
||||
v4l2_buf.0.memory = H::Memory::MEMORY_TYPE as u32;
|
||||
v4l2_buf.0.flags = qbuf.flags.bits();
|
||||
v4l2_buf.0.field = qbuf.field;
|
||||
v4l2_buf.0.sequence = qbuf.sequence;
|
||||
v4l2_buf.0.timestamp.tv_sec = qbuf.timestamp.tv_sec();
|
||||
v4l2_buf.0.timestamp.tv_usec = qbuf.timestamp.tv_usec();
|
||||
if let Some(planes) = &mut v4l2_buf.1 {
|
||||
for (dst_plane, src_plane) in planes.iter_mut().zip(qbuf.planes.into_iter()) {
|
||||
*dst_plane = src_plane.0;
|
||||
}
|
||||
} else {
|
||||
let plane = &qbuf.planes[0];
|
||||
|
||||
v4l2_buf.0.length = plane.0.length;
|
||||
v4l2_buf.0.bytesused = plane.0.bytesused;
|
||||
v4l2_buf.0.m = (&plane.0.m, H::Memory::MEMORY_TYPE).into();
|
||||
}
|
||||
|
||||
v4l2_buf
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_qbuf, b'V', 15, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
|
||||
nix::ioctl_readwrite!(vidioc_prepare_buf, b'V', 93, v4l2_buffer);
|
||||
}
|
||||
|
||||
pub type QBufError<CE> = IoctlConvertError<QBufIoctlError, CE>;
|
||||
pub type QBufResult<O, CE> = IoctlConvertResult<O, QBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QBUF` ioctl.
|
||||
///
|
||||
/// TODO: `qbuf` should be unsafe! The following invariants need to be guaranteed
|
||||
/// by the caller:
|
||||
///
|
||||
/// For MMAP buffers, any mapping must not be accessed by the caller (or any
|
||||
/// mapping must be unmapped before queueing?). Also if the buffer has been
|
||||
/// DMABUF-exported, its consumers must likewise not access it.
|
||||
///
|
||||
/// For DMABUF buffers, the FD must not be duplicated and accessed anywhere else.
|
||||
///
|
||||
/// For USERPTR buffers, things are most tricky. Not only must the data not be
|
||||
/// accessed by anyone else, the caller also needs to guarantee that the backing
|
||||
/// memory won't be freed until the corresponding buffer is returned by either
|
||||
/// `dqbuf` or `streamoff`.
|
||||
pub fn qbuf<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
|
||||
where
|
||||
I: Into<UncheckedV4l2Buffer>,
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_PREPARE_BUF` ioctl.
|
||||
pub fn prepare_buf<I, O>(fd: &impl AsRawFd, buffer: I) -> QBufResult<O, O::Error>
|
||||
where
|
||||
I: Into<UncheckedV4l2Buffer>,
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf: UncheckedV4l2Buffer = buffer.into();
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_prepare_buf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
113
libs/v4l2r/src/ioctl/querybuf.rs
Normal file
113
libs/v4l2r/src/ioctl/querybuf.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::convert::Infallible;
|
||||
use std::convert::TryFrom;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ioctl::ioctl_and_convert;
|
||||
use crate::ioctl::BufferFlags;
|
||||
use crate::ioctl::IoctlConvertError;
|
||||
use crate::ioctl::IoctlConvertResult;
|
||||
use crate::ioctl::UncheckedV4l2Buffer;
|
||||
use crate::QueueType;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueryBufPlane {
|
||||
/// Offset to pass to `mmap()` in order to obtain a mapping for this plane.
|
||||
pub mem_offset: u32,
|
||||
/// Length of this plane.
|
||||
pub length: u32,
|
||||
}
|
||||
|
||||
/// Contains information about a buffer's layout, as obtained from [`crate::ioctl::querybuf`].
|
||||
///
|
||||
/// It is a subset of [`crate::ioctl::V4l2Buffer`], only more convenient on occasion because its
|
||||
/// conversion from an unchecked v4l2_buffer cannot fail.
|
||||
///
|
||||
/// Single-planar buffers have one entry in [`planes`] representing the layout of their unique
|
||||
/// plane.
|
||||
#[derive(Debug)]
|
||||
pub struct QueryBuffer {
|
||||
pub index: usize,
|
||||
pub flags: BufferFlags,
|
||||
pub planes: Vec<QueryBufPlane>,
|
||||
}
|
||||
|
||||
impl TryFrom<UncheckedV4l2Buffer> for QueryBuffer {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(buffer: UncheckedV4l2Buffer) -> Result<Self, Self::Error> {
|
||||
let v4l2_buf = buffer.0;
|
||||
let planes = match buffer.1 {
|
||||
None => vec![QueryBufPlane {
|
||||
mem_offset: unsafe { v4l2_buf.m.offset },
|
||||
length: v4l2_buf.length,
|
||||
}],
|
||||
Some(v4l2_planes) => v4l2_planes
|
||||
.iter()
|
||||
.take(v4l2_buf.length as usize)
|
||||
.map(|v4l2_plane| QueryBufPlane {
|
||||
mem_offset: unsafe { v4l2_plane.m.mem_offset },
|
||||
length: v4l2_plane.length,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(QueryBuffer {
|
||||
index: v4l2_buf.index as usize,
|
||||
flags: BufferFlags::from_bits_truncate(v4l2_buf.flags),
|
||||
planes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_buffer;
|
||||
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryBufIoctlError {
|
||||
#[error("unsupported queue or out-of-bounds index")]
|
||||
InvalidInput,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
Other(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for QueryBufIoctlError {
|
||||
fn from(err: Errno) -> Self {
|
||||
match err {
|
||||
Errno::EINVAL => QueryBufIoctlError::InvalidInput,
|
||||
e => QueryBufIoctlError::Other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryBufIoctlError> for Errno {
|
||||
fn from(err: QueryBufIoctlError) -> Self {
|
||||
match err {
|
||||
QueryBufIoctlError::InvalidInput => Errno::EINVAL,
|
||||
QueryBufIoctlError::Other(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type QueryBufError<CE> = IoctlConvertError<QueryBufIoctlError, CE>;
|
||||
pub type QueryBufResult<O, CE> = IoctlConvertResult<O, QueryBufIoctlError, CE>;
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYBUF` ioctl.
|
||||
pub fn querybuf<O>(fd: &impl AsRawFd, queue: QueueType, index: usize) -> QueryBufResult<O, O::Error>
|
||||
where
|
||||
O: TryFrom<UncheckedV4l2Buffer>,
|
||||
O::Error: std::fmt::Debug,
|
||||
{
|
||||
let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(queue, Some(index as u32));
|
||||
|
||||
ioctl_and_convert(
|
||||
unsafe { ioctl::vidioc_querybuf(fd.as_raw_fd(), v4l2_buf.as_mut()) }
|
||||
.map(|_| v4l2_buf)
|
||||
.map_err(Into::into),
|
||||
)
|
||||
}
|
||||
136
libs/v4l2r/src/ioctl/querycap.rs
Normal file
136
libs/v4l2r/src/ioctl/querycap.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
//! Safe wrapper for the `VIDIOC_QUERYCAP` ioctl.
|
||||
use super::string_from_cstr;
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_capability;
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use std::fmt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_QUERYCAP` ioctl into the `capabilities`
|
||||
/// or `device_capabilities` field of `v4l2_capability`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Capabilities: u32 {
|
||||
const VIDEO_CAPTURE = bindings::V4L2_CAP_VIDEO_CAPTURE;
|
||||
const VIDEO_OUTPUT = bindings::V4L2_CAP_VIDEO_OUTPUT;
|
||||
const VIDEO_OVERLAY = bindings::V4L2_CAP_VIDEO_OVERLAY;
|
||||
const VBI_CAPTURE = bindings::V4L2_CAP_VBI_CAPTURE;
|
||||
const VBI_OUTPUT = bindings::V4L2_CAP_VBI_OUTPUT;
|
||||
const SLICED_VBI_CAPTURE = bindings::V4L2_CAP_SLICED_VBI_CAPTURE;
|
||||
const SLICED_VBI_OUTPUT = bindings::V4L2_CAP_SLICED_VBI_OUTPUT;
|
||||
const RDS_CAPTURE = bindings::V4L2_CAP_RDS_CAPTURE;
|
||||
const VIDEO_OUTPUT_OVERLAY = bindings::V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
|
||||
const HW_FREQ_SEEK = bindings::V4L2_CAP_HW_FREQ_SEEK;
|
||||
const RDS_OUTPUT = bindings::V4L2_CAP_RDS_OUTPUT;
|
||||
|
||||
const VIDEO_CAPTURE_MPLANE = bindings::V4L2_CAP_VIDEO_CAPTURE_MPLANE;
|
||||
const VIDEO_OUTPUT_MPLANE = bindings::V4L2_CAP_VIDEO_OUTPUT_MPLANE;
|
||||
const VIDEO_M2M_MPLANE = bindings::V4L2_CAP_VIDEO_M2M_MPLANE;
|
||||
const VIDEO_M2M = bindings::V4L2_CAP_VIDEO_M2M;
|
||||
|
||||
const TUNER = bindings::V4L2_CAP_TUNER;
|
||||
const AUDIO = bindings::V4L2_CAP_AUDIO;
|
||||
const RADIO = bindings::V4L2_CAP_RADIO;
|
||||
const MODULATOR = bindings::V4L2_CAP_MODULATOR;
|
||||
|
||||
const SDR_CAPTURE = bindings::V4L2_CAP_SDR_CAPTURE;
|
||||
const EXT_PIX_FORMAT = bindings::V4L2_CAP_EXT_PIX_FORMAT;
|
||||
const SDR_OUTPUT = bindings::V4L2_CAP_SDR_OUTPUT;
|
||||
const META_CAPTURE = bindings::V4L2_CAP_META_CAPTURE;
|
||||
|
||||
const READWRITE = bindings::V4L2_CAP_READWRITE;
|
||||
const ASYNCIO = bindings::V4L2_CAP_ASYNCIO;
|
||||
const STREAMING = bindings::V4L2_CAP_STREAMING;
|
||||
const META_OUTPUT = bindings::V4L2_CAP_META_OUTPUT;
|
||||
|
||||
const TOUCH = bindings::V4L2_CAP_TOUCH;
|
||||
|
||||
const DEVICE_CAPS = bindings::V4L2_CAP_DEVICE_CAPS;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Capabilities {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to get the capability flags from a `VIDIOC_QUERYCAP` ioctl.
|
||||
impl From<v4l2_capability> for Capabilities {
|
||||
fn from(qcap: v4l2_capability) -> Self {
|
||||
Capabilities::from_bits_truncate(qcap.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe variant of the `v4l2_capability` struct, to be used with `querycap`.
|
||||
#[derive(Debug)]
|
||||
pub struct Capability {
|
||||
pub driver: String,
|
||||
pub card: String,
|
||||
pub bus_info: String,
|
||||
pub version: u32,
|
||||
pub capabilities: Capabilities,
|
||||
pub device_caps: Option<Capabilities>,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Returns the set of capabilities of the hardware as a whole.
|
||||
pub fn capabilities(&self) -> Capabilities {
|
||||
self.capabilities
|
||||
}
|
||||
|
||||
/// Returns the capabilities that apply to the currently opened V4L2 node.
|
||||
pub fn device_caps(&self) -> Capabilities {
|
||||
self.device_caps
|
||||
.unwrap_or_else(|| self.capabilities.difference(Capabilities::DEVICE_CAPS))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_capability> for Capability {
|
||||
fn from(qcap: v4l2_capability) -> Self {
|
||||
Capability {
|
||||
driver: string_from_cstr(&qcap.driver).unwrap_or_else(|_| "".into()),
|
||||
card: string_from_cstr(&qcap.card).unwrap_or_else(|_| "".into()),
|
||||
bus_info: string_from_cstr(&qcap.bus_info).unwrap_or_else(|_| "".into()),
|
||||
version: qcap.version,
|
||||
capabilities: Capabilities::from_bits_truncate(qcap.capabilities),
|
||||
device_caps: if qcap.capabilities & bindings::V4L2_CAP_DEVICE_CAPS != 0 {
|
||||
Some(Capabilities::from_bits_truncate(qcap.device_caps))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_capability;
|
||||
nix::ioctl_read!(vidioc_querycap, b'V', 0, v4l2_capability);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QueryCapError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<QueryCapError> for Errno {
|
||||
fn from(err: QueryCapError) -> Self {
|
||||
match err {
|
||||
QueryCapError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_QUERYCAP` ioctl.
|
||||
pub fn querycap<T: From<v4l2_capability>>(fd: &impl AsRawFd) -> Result<T, QueryCapError> {
|
||||
let mut qcap: v4l2_capability = Default::default();
|
||||
|
||||
match unsafe { ioctl::vidioc_querycap(fd.as_raw_fd(), &mut qcap) } {
|
||||
Ok(_) => Ok(T::from(qcap)),
|
||||
Err(e) => Err(QueryCapError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
162
libs/v4l2r/src/ioctl/reqbufs.rs
Normal file
162
libs/v4l2r/src/ioctl/reqbufs.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! Safe wrapper for the `VIDIOC_REQBUFS` ioctl.
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_create_buffers;
|
||||
use crate::bindings::v4l2_format;
|
||||
use crate::bindings::v4l2_requestbuffers;
|
||||
use crate::memory::MemoryType;
|
||||
use crate::QueueType;
|
||||
use bitflags::bitflags;
|
||||
use nix::{self, errno::Errno};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
bitflags! {
|
||||
/// Flags returned by the `VIDIOC_REQBUFS` ioctl into the `capabilities`
|
||||
/// field of `struct v4l2_requestbuffers`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BufferCapabilities: u32 {
|
||||
const SUPPORTS_MMAP = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP;
|
||||
const SUPPORTS_USERPTR = bindings::V4L2_BUF_CAP_SUPPORTS_USERPTR;
|
||||
const SUPPORTS_DMABUF = bindings::V4L2_BUF_CAP_SUPPORTS_DMABUF;
|
||||
const SUPPORTS_REQUESTS = bindings::V4L2_BUF_CAP_SUPPORTS_REQUESTS;
|
||||
const SUPPORTS_ORPHANED_BUFS = bindings::V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS;
|
||||
const SUPPORTS_M2M_HOLD_CAPTURE_BUF = bindings::V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF;
|
||||
const SUPPORTS_MMAP_CACHE_HINTS = bindings::V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Memory Consistency Flags passed to the `VIDIOC_REQBUFS` ioctl in the `flags`
|
||||
/// field of `struct v4l2_requestbuffers`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MemoryConsistency: u8 {
|
||||
const MEMORY_FLAG_NON_COHERENT = bindings::V4L2_MEMORY_FLAG_NON_COHERENT as u8;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4l2_requestbuffers> for () {
|
||||
fn from(_reqbufs: v4l2_requestbuffers) -> Self {}
|
||||
}
|
||||
|
||||
/// In case we are just interested in the number of buffers that `reqbufs`
|
||||
/// created.
|
||||
impl From<v4l2_requestbuffers> for usize {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
reqbufs.count as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// If we just want to query the buffer capabilities.
|
||||
impl From<v4l2_requestbuffers> for BufferCapabilities {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
BufferCapabilities::from_bits_truncate(reqbufs.capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Full result of the `reqbufs` ioctl.
|
||||
pub struct RequestBuffers {
|
||||
pub count: u32,
|
||||
pub capabilities: BufferCapabilities,
|
||||
pub flags: MemoryConsistency,
|
||||
}
|
||||
|
||||
impl From<v4l2_requestbuffers> for RequestBuffers {
|
||||
fn from(reqbufs: v4l2_requestbuffers) -> Self {
|
||||
RequestBuffers {
|
||||
count: reqbufs.count,
|
||||
capabilities: BufferCapabilities::from_bits_truncate(reqbufs.capabilities),
|
||||
flags: MemoryConsistency::from_bits_truncate(reqbufs.flags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::v4l2_create_buffers;
|
||||
use crate::bindings::v4l2_requestbuffers;
|
||||
|
||||
nix::ioctl_readwrite!(vidioc_reqbufs, b'V', 8, v4l2_requestbuffers);
|
||||
nix::ioctl_readwrite!(vidioc_create_bufs, b'V', 92, v4l2_create_buffers);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ReqbufsError {
|
||||
#[error("invalid buffer ({0}) or memory type ({1:?}) requested")]
|
||||
InvalidBufferType(QueueType, MemoryType),
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<ReqbufsError> for Errno {
|
||||
fn from(err: ReqbufsError) -> Self {
|
||||
match err {
|
||||
ReqbufsError::InvalidBufferType(_, _) => Errno::EINVAL,
|
||||
ReqbufsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_REQBUFS` ioctl.
|
||||
pub fn reqbufs<O: From<v4l2_requestbuffers>>(
|
||||
fd: &impl AsRawFd,
|
||||
queue: QueueType,
|
||||
memory: MemoryType,
|
||||
count: u32,
|
||||
flags: MemoryConsistency,
|
||||
) -> Result<O, ReqbufsError> {
|
||||
let mut reqbufs = v4l2_requestbuffers {
|
||||
count,
|
||||
type_: queue as u32,
|
||||
memory: memory as u32,
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_reqbufs(fd.as_raw_fd(), &mut reqbufs) } {
|
||||
Ok(_) => Ok(O::from(reqbufs)),
|
||||
Err(Errno::EINVAL) => Err(ReqbufsError::InvalidBufferType(queue, memory)),
|
||||
Err(e) => Err(ReqbufsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CreateBufsError {
|
||||
#[error("no memory available to allocate MMAP buffers")]
|
||||
NoMem,
|
||||
#[error("invalid format or memory type requested")]
|
||||
Invalid,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(nix::Error),
|
||||
}
|
||||
|
||||
impl From<CreateBufsError> for Errno {
|
||||
fn from(err: CreateBufsError) -> Self {
|
||||
match err {
|
||||
CreateBufsError::NoMem => Errno::ENOMEM,
|
||||
CreateBufsError::Invalid => Errno::EINVAL,
|
||||
CreateBufsError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_CREATE_BUFS` ioctl.
|
||||
pub fn create_bufs<F: Into<v4l2_format>, O: From<v4l2_create_buffers>>(
|
||||
fd: &impl AsRawFd,
|
||||
count: u32,
|
||||
memory: MemoryType,
|
||||
format: F,
|
||||
) -> Result<O, CreateBufsError> {
|
||||
let mut create_bufs = v4l2_create_buffers {
|
||||
count,
|
||||
memory: memory as u32,
|
||||
format: format.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match unsafe { ioctl::vidioc_create_bufs(fd.as_raw_fd(), &mut create_bufs) } {
|
||||
Ok(_) => Ok(O::from(create_bufs)),
|
||||
Err(Errno::ENOMEM) => Err(CreateBufsError::NoMem),
|
||||
Err(Errno::EINVAL) => Err(CreateBufsError::Invalid),
|
||||
Err(e) => Err(CreateBufsError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
71
libs/v4l2r/src/ioctl/streamon.rs
Normal file
71
libs/v4l2r/src/ioctl/streamon.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Safe wrapper for the `VIDIOC_STREAM(ON|OFF)` ioctls.
|
||||
use crate::QueueType;
|
||||
use nix::errno::Errno;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
nix::ioctl_write_ptr!(vidioc_streamon, b'V', 18, u32);
|
||||
nix::ioctl_write_ptr!(vidioc_streamoff, b'V', 19, u32);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StreamOnError {
|
||||
#[error("queue type ({0}) not supported, or no buffers allocated or enqueued")]
|
||||
InvalidQueue(QueueType),
|
||||
#[error("invalid pad configuration")]
|
||||
InvalidPadConfig,
|
||||
#[error("invalid pipeline link configuration")]
|
||||
InvalidPipelineConfig,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<StreamOnError> for Errno {
|
||||
fn from(err: StreamOnError) -> Self {
|
||||
match err {
|
||||
StreamOnError::InvalidQueue(_) => Errno::EINVAL,
|
||||
StreamOnError::InvalidPadConfig => Errno::EPIPE,
|
||||
StreamOnError::InvalidPipelineConfig => Errno::ENOLINK,
|
||||
StreamOnError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_STREAMON` ioctl.
|
||||
pub fn streamon(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOnError> {
|
||||
match unsafe { ioctl::vidioc_streamon(fd.as_raw_fd(), &(queue as u32)) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(StreamOnError::InvalidQueue(queue)),
|
||||
Err(Errno::EPIPE) => Err(StreamOnError::InvalidPadConfig),
|
||||
Err(Errno::ENOLINK) => Err(StreamOnError::InvalidPipelineConfig),
|
||||
Err(e) => Err(StreamOnError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StreamOffError {
|
||||
#[error("queue type not supported")]
|
||||
InvalidQueue,
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<StreamOffError> for Errno {
|
||||
fn from(err: StreamOffError) -> Self {
|
||||
match err {
|
||||
StreamOffError::InvalidQueue => Errno::EINVAL,
|
||||
StreamOffError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_STREAMOFF` ioctl.
|
||||
pub fn streamoff(fd: &impl AsRawFd, queue: QueueType) -> Result<(), StreamOffError> {
|
||||
match unsafe { ioctl::vidioc_streamoff(fd.as_raw_fd(), &(queue as u32)) } {
|
||||
Ok(_) => Ok(()),
|
||||
Err(Errno::EINVAL) => Err(StreamOffError::InvalidQueue),
|
||||
Err(e) => Err(StreamOffError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
210
libs/v4l2r/src/ioctl/subscribe_event.rs
Normal file
210
libs/v4l2r/src/ioctl/subscribe_event.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! Safe wrapper for the `VIDIOC_SUBSCRIBE_EVENT` and `VIDIOC_UNSUBSCRIBE_EVENT
|
||||
//! ioctls.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::bindings::v4l2_event;
|
||||
use crate::bindings::v4l2_event_subscription;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SubscribeEventFlags: u32 {
|
||||
const SEND_INITIAL = bindings::V4L2_EVENT_SUB_FL_SEND_INITIAL;
|
||||
const ALLOW_FEEDBACK = bindings::V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventType {
|
||||
VSync,
|
||||
Eos,
|
||||
Ctrl(u32),
|
||||
FrameSync,
|
||||
SourceChange(u32),
|
||||
MotionDet,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EventConversionError {
|
||||
#[error("unrecognized event {0}")]
|
||||
UnrecognizedEvent(u32),
|
||||
#[error("unrecognized source change {0}")]
|
||||
UnrecognizedSourceChange(u32),
|
||||
}
|
||||
|
||||
impl TryFrom<&v4l2_event_subscription> for EventType {
|
||||
type Error = EventConversionError;
|
||||
|
||||
fn try_from(event: &v4l2_event_subscription) -> Result<Self, Self::Error> {
|
||||
Ok(match event.type_ {
|
||||
bindings::V4L2_EVENT_VSYNC => EventType::VSync,
|
||||
bindings::V4L2_EVENT_EOS => EventType::Eos,
|
||||
bindings::V4L2_EVENT_CTRL => EventType::Ctrl(event.id),
|
||||
bindings::V4L2_EVENT_FRAME_SYNC => EventType::FrameSync,
|
||||
bindings::V4L2_EVENT_SOURCE_CHANGE => EventType::SourceChange(event.id),
|
||||
bindings::V4L2_EVENT_MOTION_DET => EventType::MotionDet,
|
||||
e => return Err(EventConversionError::UnrecognizedEvent(e)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SrcChanges: u32 {
|
||||
const RESOLUTION = bindings::V4L2_EVENT_SRC_CH_RESOLUTION;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
SrcChangeEvent(SrcChanges),
|
||||
Eos,
|
||||
}
|
||||
|
||||
impl TryFrom<v4l2_event> for Event {
|
||||
type Error = EventConversionError;
|
||||
|
||||
fn try_from(value: v4l2_event) -> Result<Self, Self::Error> {
|
||||
Ok(match value.type_ {
|
||||
bindings::V4L2_EVENT_VSYNC => todo!(),
|
||||
bindings::V4L2_EVENT_EOS => Event::Eos,
|
||||
bindings::V4L2_EVENT_CTRL => todo!(),
|
||||
bindings::V4L2_EVENT_FRAME_SYNC => todo!(),
|
||||
bindings::V4L2_EVENT_SOURCE_CHANGE => {
|
||||
let changes = unsafe { value.u.src_change.changes };
|
||||
Event::SrcChangeEvent(
|
||||
SrcChanges::from_bits(changes)
|
||||
.ok_or(EventConversionError::UnrecognizedSourceChange(changes))?,
|
||||
)
|
||||
}
|
||||
bindings::V4L2_EVENT_MOTION_DET => todo!(),
|
||||
t => return Err(EventConversionError::UnrecognizedEvent(t)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn build_v4l2_event_subscription(
|
||||
event: EventType,
|
||||
flags: SubscribeEventFlags,
|
||||
) -> v4l2_event_subscription {
|
||||
v4l2_event_subscription {
|
||||
type_: match event {
|
||||
EventType::VSync => bindings::V4L2_EVENT_VSYNC,
|
||||
EventType::Eos => bindings::V4L2_EVENT_EOS,
|
||||
EventType::Ctrl(_) => bindings::V4L2_EVENT_CTRL,
|
||||
EventType::FrameSync => bindings::V4L2_EVENT_FRAME_SYNC,
|
||||
EventType::SourceChange(_) => bindings::V4L2_EVENT_SOURCE_CHANGE,
|
||||
EventType::MotionDet => bindings::V4L2_EVENT_MOTION_DET,
|
||||
},
|
||||
id: match event {
|
||||
EventType::Ctrl(id) => id,
|
||||
EventType::SourceChange(id) => id,
|
||||
_ => 0,
|
||||
},
|
||||
flags: flags.bits(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
mod ioctl {
|
||||
use crate::bindings::{v4l2_event, v4l2_event_subscription};
|
||||
|
||||
nix::ioctl_read!(vidioc_dqevent, b'V', 89, v4l2_event);
|
||||
nix::ioctl_write_ptr!(vidioc_subscribe_event, b'V', 90, v4l2_event_subscription);
|
||||
nix::ioctl_write_ptr!(vidioc_unsubscribe_event, b'V', 91, v4l2_event_subscription);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SubscribeEventError {
|
||||
#[error("ioctl error: {0}")]
|
||||
IoctlError(#[from] Errno),
|
||||
}
|
||||
|
||||
impl From<SubscribeEventError> for Errno {
|
||||
fn from(err: SubscribeEventError) -> Self {
|
||||
match err {
|
||||
SubscribeEventError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_SUBSCRIBE_EVENT` ioctl.
|
||||
pub fn subscribe_event(
|
||||
fd: &impl AsRawFd,
|
||||
event: EventType,
|
||||
flags: SubscribeEventFlags,
|
||||
) -> Result<(), SubscribeEventError> {
|
||||
let subscription = build_v4l2_event_subscription(event, flags);
|
||||
|
||||
unsafe { ioctl::vidioc_subscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl.
|
||||
pub fn unsubscribe_event(fd: &impl AsRawFd, event: EventType) -> Result<(), SubscribeEventError> {
|
||||
let subscription = build_v4l2_event_subscription(event, SubscribeEventFlags::empty());
|
||||
|
||||
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `VIDIOC_UNSUBSCRIBE_EVENT` ioctl to unsubscribe from all events.
|
||||
pub fn unsubscribe_all_events(fd: &impl AsRawFd) -> Result<(), SubscribeEventError> {
|
||||
let subscription = v4l2_event_subscription {
|
||||
type_: bindings::V4L2_EVENT_ALL,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe { ioctl::vidioc_unsubscribe_event(fd.as_raw_fd(), &subscription) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DqEventError {
|
||||
#[error("no event ready for dequeue")]
|
||||
NotReady,
|
||||
#[error("error while converting event")]
|
||||
EventConversionError,
|
||||
#[error("unexpected ioctl error: {0}")]
|
||||
IoctlError(Errno),
|
||||
}
|
||||
|
||||
impl From<Errno> for DqEventError {
|
||||
fn from(error: Errno) -> Self {
|
||||
match error {
|
||||
Errno::ENOENT => Self::NotReady,
|
||||
error => Self::IoctlError(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DqEventError> for Errno {
|
||||
fn from(err: DqEventError) -> Self {
|
||||
match err {
|
||||
DqEventError::NotReady => Errno::ENOENT,
|
||||
DqEventError::EventConversionError => Errno::EINVAL,
|
||||
DqEventError::IoctlError(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dqevent<O: TryFrom<v4l2_event>>(fd: &impl AsRawFd) -> Result<O, DqEventError> {
|
||||
let mut event: v4l2_event = Default::default();
|
||||
|
||||
match unsafe { ioctl::vidioc_dqevent(fd.as_raw_fd(), &mut event) } {
|
||||
Ok(_) => Ok(event
|
||||
.try_into()
|
||||
.map_err(|_| DqEventError::EventConversionError)?),
|
||||
Err(Errno::ENOENT) => Err(DqEventError::NotReady),
|
||||
Err(e) => Err(DqEventError::IoctlError(e)),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user