| //! This module provides safer versions of the V4L2 ioctls through simple functions working on a |
| //! `RawFd`, and safer variants of the main V4L2 structures. This module can be used directly, but |
| //! the `device` module is very likely to be a better fit for application code. |
| //! |
| //! V4L2 ioctls are usually called with a single structure as argument, which serves to store both |
| //! the input and output of the ioctl. This is quite error-prone as the user needs to remember |
| //! which parts of the structure they are supposed to fill, and which parts the driver will update. |
| //! |
| //! To alleviate this issue, this module tries to provide, for each ioctl: |
| //! |
| //! Consequently, each ioctl proxy function is designed as follows: |
| //! |
| //! * A function that takes the relevant input as parameters and not the entire input/output |
| //! structure. This lifts any ambiguity as to which parts of the structure userspace is supposed to |
| //! fill. |
| //! * Safe variants of V4L2 structures used in ioctls that can be build from their C counterparts |
| //! (and vice-versa) and include a validation step, to be used as return values. |
| //! |
| //! For instance, the `VIDIOC_G_FMT` ioctl takes a `struct v4l2_format` as argument, but only the |
| //! its `type` field is set by user-space - the rest of the structure is to be filled by the |
| //! driver. |
| //! |
| //! Therefore, our [`crate::ioctl::g_fmt()`] ioctl function takes the requested queue type as |
| //! argument and takes care of managing the `struct v4l2_format` to be passed to the kernel. The |
| //! filled structure is then converted into the type desired by the caller using |
| //! `TryFrom<v4l2_format>`: |
| //! |
| //! ```text |
| //! pub fn g_fmt<O: TryFrom<bindings::v4l2_format>>( |
| //! fd: &impl AsRawFd, |
| //! queue: QueueType, |
| //! ) -> Result<O, GFmtError>; |
| //! ``` |
| //! |
| //! Since `struct v4l2_format` has C unions that are unsafe to use in Rust, the [`crate::Format`] |
| //! type can be used as the output type of this function, to validate the `struct v4l2_format` |
| //! returned by the kernel and convert it to a safe type. |
| //! |
| //! Most ioctls also have their own error type: this helps discern scenarios where the ioctl |
| //! returned non-zero, but the situation is not necessarily an error. For instance, `VIDIOC_DQBUF` |
| //! can return -EAGAIN if no buffer is available to dequeue, which is not an error and thus is |
| //! represented by its own variant. Actual errors are captured by the `IoctlError` variant, and all |
| //! error types can be converted to their original error code using their `Into<Errno>` |
| //! implementation. |
| |
| mod decoder_cmd; |
| mod dqbuf; |
| mod encoder_cmd; |
| mod enum_fmt; |
| mod expbuf; |
| mod frameintervals; |
| mod framesizes; |
| mod g_audio; |
| mod g_dv_timings; |
| mod g_ext_ctrls; |
| mod g_fmt; |
| mod g_input; |
| mod g_jpegcomp; |
| mod g_parm; |
| mod g_selection; |
| mod mmap; |
| mod qbuf; |
| mod querybuf; |
| mod querycap; |
| mod queryctrl; |
| mod reqbufs; |
| mod request; |
| mod streamon; |
| mod subscribe_event; |
| |
| pub use decoder_cmd::*; |
| pub use dqbuf::*; |
| pub use encoder_cmd::*; |
| pub use enum_fmt::*; |
| pub use expbuf::*; |
| pub use frameintervals::*; |
| pub use framesizes::*; |
| pub use g_audio::*; |
| pub use g_dv_timings::*; |
| pub use g_ext_ctrls::*; |
| pub use g_fmt::*; |
| pub use g_input::*; |
| pub use g_jpegcomp::*; |
| pub use g_parm::*; |
| pub use g_selection::*; |
| pub use mmap::*; |
| pub use qbuf::*; |
| pub use querybuf::*; |
| pub use querycap::*; |
| pub use queryctrl::*; |
| pub use reqbufs::*; |
| pub use request::*; |
| pub use streamon::*; |
| pub use subscribe_event::*; |
| |
| use std::convert::Infallible; |
| use std::convert::TryFrom; |
| use std::ffi::CStr; |
| use std::ffi::FromBytesWithNulError; |
| use std::fmt::Debug; |
| use std::ops::Deref; |
| use std::ops::DerefMut; |
| |
| use bitflags::bitflags; |
| use enumn::N; |
| use nix::errno::Errno; |
| use thiserror::Error; |
| |
| use crate::bindings; |
| use crate::memory::DmaBuf; |
| use crate::memory::Memory; |
| use crate::memory::MemoryType; |
| use crate::memory::Mmap; |
| use crate::memory::UserPtr; |
| use crate::Colorspace; |
| use crate::PixelFormat; |
| use crate::Quantization; |
| use crate::QueueDirection; |
| use crate::QueueType; |
| use crate::XferFunc; |
| use crate::YCbCrEncoding; |
| |
| /// Utility function for sub-modules. |
| /// Constructs an owned String instance from a slice containing a nul-terminated |
| /// C string, after checking that the passed slice indeed contains a nul |
| /// character. |
| fn string_from_cstr(c_str: &[u8]) -> Result<String, FromBytesWithNulError> { |
| // Make sure that our string contains a nul character. |
| let slice = match c_str.iter().position(|x| *x == b'\0') { |
| // Pass the full slice, `from_bytes_with_nul` will return an error. |
| None => c_str, |
| Some(pos) => &c_str[..pos + 1], |
| }; |
| |
| Ok(CStr::from_bytes_with_nul(slice)? |
| .to_string_lossy() |
| .into_owned()) |
| } |
| |
| /// Extension trait for allowing easy conversion of ioctl errors into their originating error code. |
| pub trait IntoErrno { |
| fn into_errno(self) -> i32; |
| } |
| |
| impl<T> IntoErrno for T |
| where |
| T: Into<Errno>, |
| { |
| fn into_errno(self) -> i32 { |
| self.into() as i32 |
| } |
| } |
| |
| /// Error type for a "run ioctl and try to convert to safer type" operation. |
| /// |
| /// [`IoctlError`] means that the ioctl itself has failed, while [`ConversionError`] indicates that |
| /// the output of the ioctl could not be converted to the desired output type for the ioctl |
| #[derive(Debug, Error)] |
| pub enum IoctlConvertError<IE: Debug, CE: Debug> { |
| #[error("error during ioctl: {0}")] |
| IoctlError(#[from] IE), |
| #[error("error while converting ioctl result: {0}")] |
| ConversionError(CE), |
| } |
| |
| impl<IE, CE> IntoErrno for IoctlConvertError<IE, CE> |
| where |
| IE: Debug + Into<Errno>, |
| CE: Debug, |
| { |
| fn into_errno(self) -> i32 { |
| match self { |
| IoctlConvertError::IoctlError(e) => e.into_errno(), |
| IoctlConvertError::ConversionError(_) => Errno::EINVAL as i32, |
| } |
| } |
| } |
| |
| // We need a bound here, otherwise we cannot use `O::Error`. |
| #[allow(type_alias_bounds)] |
| pub type IoctlConvertResult<O, IE, CE> = Result<O, IoctlConvertError<IE, CE>>; |
| |
| /// Tries to convert the raw output of an ioctl to a safer type. |
| /// |
| /// Ioctl wrappers always return a raw C type that most of the case is potentially invalid: for |
| /// instance C enums might have invalid values. |
| /// |
| /// This function takes a raw ioctl result and, if successful, attempts to convert its output to a |
| /// safer type using [`TryFrom`]. If either the ioctl or the conversion fails, then the appropriate |
| /// variant of [`IoctlConvertError`] is returned. |
| fn ioctl_and_convert<I, O, IE>(res: Result<I, IE>) -> IoctlConvertResult<O, IE, O::Error> |
| where |
| IE: std::fmt::Debug, |
| O: TryFrom<I>, |
| O::Error: std::fmt::Debug, |
| { |
| res.map_err(IoctlConvertError::IoctlError) |
| .and_then(|o| O::try_from(o).map_err(IoctlConvertError::ConversionError)) |
| } |
| |
| /// A fully owned V4L2 buffer obtained from some untrusted place (typically an ioctl), or created |
| /// with the purpose of receiving the result of an ioctl. |
| /// |
| /// For any serious use it should be converted into something safer like [`V4l2Buffer`]. |
| pub struct UncheckedV4l2Buffer(pub bindings::v4l2_buffer, pub Option<V4l2BufferPlanes>); |
| |
| impl UncheckedV4l2Buffer { |
| /// Returns a new buffer with the queue type set to `queue` and its index to `index`. |
| /// |
| /// If `queue` is multiplanar, then the number of planes will be set to `VIDEO_MAX_PLANES` so |
| /// the buffer can receive the result of ioctl that write into a `v4l2_buffer` such as |
| /// `VIDIOC_QUERYBUF` or `VIDIOC_DQBUF`. [`as_mut`] can be called in order to obtain a |
| /// reference to the buffer with its `planes` pointer properly set. |
| pub fn new_for_querybuf(queue: QueueType, index: Option<u32>) -> Self { |
| let multiplanar = queue.is_multiplanar(); |
| |
| UncheckedV4l2Buffer( |
| bindings::v4l2_buffer { |
| index: index.unwrap_or_default(), |
| type_: queue as u32, |
| length: if multiplanar { |
| bindings::VIDEO_MAX_PLANES |
| } else { |
| Default::default() |
| }, |
| ..Default::default() |
| }, |
| if multiplanar { |
| Some(Default::default()) |
| } else { |
| None |
| }, |
| ) |
| } |
| } |
| |
| /// For cases where we are not interested in the result of `qbuf` |
| impl TryFrom<UncheckedV4l2Buffer> for () { |
| type Error = Infallible; |
| |
| fn try_from(_: UncheckedV4l2Buffer) -> Result<Self, Self::Error> { |
| Ok(()) |
| } |
| } |
| |
| impl From<V4l2Buffer> for UncheckedV4l2Buffer { |
| fn from(buffer: V4l2Buffer) -> Self { |
| let is_multiplanar = buffer.queue().is_multiplanar(); |
| |
| Self( |
| buffer.buffer, |
| if is_multiplanar { |
| Some(buffer.planes) |
| } else { |
| None |
| }, |
| ) |
| } |
| } |
| |
| /// Returns a mutable pointer to the buffer after making sure its plane pointer is valid, if the |
| /// buffer is multiplanar. |
| /// |
| /// This should be used to make sure the buffer is not going to move as long as the reference is |
| /// alive. |
| impl AsMut<bindings::v4l2_buffer> for UncheckedV4l2Buffer { |
| fn as_mut(&mut self) -> &mut bindings::v4l2_buffer { |
| match (QueueType::n(self.0.type_), &mut self.1) { |
| (Some(queue), Some(planes)) if queue.is_multiplanar() => { |
| self.0.m.planes = planes.as_mut_ptr() |
| } |
| _ => (), |
| } |
| |
| &mut self.0 |
| } |
| } |
| |
| /// A memory area we can pass to ioctls in order to get/set plane information |
| /// with the multi-planar API. |
| type V4l2BufferPlanes = [bindings::v4l2_plane; bindings::VIDEO_MAX_PLANES as usize]; |
| |
| bitflags! { |
| #[derive(Clone, Copy, Debug, Default)] |
| /// `flags` member of `struct `v4l2_buffer`. |
| pub struct BufferFlags: u32 { |
| const MAPPED = bindings::V4L2_BUF_FLAG_MAPPED; |
| const QUEUED = bindings::V4L2_BUF_FLAG_QUEUED; |
| const DONE = bindings::V4L2_BUF_FLAG_DONE; |
| const ERROR = bindings::V4L2_BUF_FLAG_ERROR; |
| const KEYFRAME = bindings::V4L2_BUF_FLAG_KEYFRAME; |
| const PFRAME = bindings::V4L2_BUF_FLAG_PFRAME; |
| const BFRAME = bindings::V4L2_BUF_FLAG_BFRAME; |
| const TIMECODE = bindings::V4L2_BUF_FLAG_TIMECODE; |
| const PREPARED = bindings::V4L2_BUF_FLAG_PREPARED; |
| const NO_CACHE_INVALIDATE = bindings::V4L2_BUF_FLAG_NO_CACHE_CLEAN; |
| const NO_CACHE_CLEAN = bindings::V4L2_BUF_FLAG_NO_CACHE_INVALIDATE; |
| const LAST = bindings::V4L2_BUF_FLAG_LAST; |
| const TIMESTAMP_MONOTONIC = bindings::V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; |
| const TIMESTAMP_COPY = bindings::V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| const TSTAMP_SRC_EOF = bindings::V4L2_BUF_FLAG_TSTAMP_SRC_EOF; |
| const TSTAMP_SRC_SOE = bindings::V4L2_BUF_FLAG_TSTAMP_SRC_SOE; |
| const REQUEST_FD = bindings::V4L2_BUF_FLAG_REQUEST_FD; |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, N)] |
| #[repr(u32)] |
| pub enum BufferField { |
| #[default] |
| Any = bindings::v4l2_field_V4L2_FIELD_ANY, |
| None = bindings::v4l2_field_V4L2_FIELD_NONE, |
| Top = bindings::v4l2_field_V4L2_FIELD_TOP, |
| Interlaced = bindings::v4l2_field_V4L2_FIELD_INTERLACED, |
| SeqTb = bindings::v4l2_field_V4L2_FIELD_SEQ_TB, |
| SeqBt = bindings::v4l2_field_V4L2_FIELD_SEQ_BT, |
| Alternate = bindings::v4l2_field_V4L2_FIELD_ALTERNATE, |
| InterlacedTb = bindings::v4l2_field_V4L2_FIELD_INTERLACED_TB, |
| InterlacedBt = bindings::v4l2_field_V4L2_FIELD_INTERLACED_BT, |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum V4l2BufferResizePlanesError { |
| #[error("zero planes requested")] |
| ZeroPlanesRequested, |
| #[error("buffer is single planar and can only accomodate one plane")] |
| SinglePlanar, |
| #[error("more than VIDEO_MAX_PLANES have been requested")] |
| TooManyPlanes, |
| } |
| |
| /// Safe-ish representation of a `struct v4l2_buffer`. It owns its own planes array and can only be |
| /// constructed from valid data. |
| /// |
| /// This structure guarantees the following invariants: |
| /// |
| /// * The buffer's queue type is valid and cannot change, |
| /// * The buffer's memory type is valid and cannot change, |
| /// * The memory backing (MMAP offset/user pointer/DMABUF) can only be read and set according to |
| /// the memory type of the buffer. I.e. it is impossible to mistakenly set `fd` unless the |
| /// buffer's memory type is `DMABUF`. |
| /// * Single-planar buffers can only have exactly one and only one plane. |
| /// |
| /// Planes management is a bit complicated due to the existence of the single-planar and a |
| /// multi-planar buffer representations. There are situations where one wants to access plane |
| /// information regardless of the representation used, and others where one wants to access the |
| /// actual array of `struct v4l2_plane`, provided it exists. |
| /// |
| /// For the first situation, use the `planes_iter` and `planes_iter_mut` methods. They return an |
| /// iterator to an accessor to plane data that is identical whether the buffer is single or multi |
| /// planar (or course, for single-planar buffers the length of the iterator will be exactly 1). |
| /// |
| /// For the second situation, the `as_v4l2_planes` method returns an actual slice of `struct |
| /// v4l2_plane` with the plane information if the buffer is multi-planar (and an empty slice if the |
| /// it is single-planar). |
| #[derive(Clone)] |
| #[repr(C)] |
| pub struct V4l2Buffer { |
| buffer: bindings::v4l2_buffer, |
| planes: V4l2BufferPlanes, |
| } |
| |
| /// V4l2Buffer is safe to send across threads. `v4l2_buffer` is !Send & !Sync |
| /// because it contains a pointer, but we are making sure to use it safely here. |
| unsafe impl Send for V4l2Buffer {} |
| unsafe impl Sync for V4l2Buffer {} |
| |
| impl Debug for V4l2Buffer { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| f.debug_struct("V4l2Buffer") |
| .field("index", &self.index()) |
| .field("flags", &self.flags()) |
| .field("sequence", &self.sequence()) |
| .finish() |
| } |
| } |
| |
| impl V4l2Buffer { |
| pub fn new(queue: QueueType, index: u32, memory: MemoryType) -> Self { |
| Self { |
| buffer: bindings::v4l2_buffer { |
| index, |
| type_: queue as u32, |
| memory: memory as u32, |
| // Make sure that a multiplanar buffer always has at least one plane. |
| length: if queue.is_multiplanar() { |
| 1 |
| } else { |
| Default::default() |
| }, |
| ..Default::default() |
| }, |
| planes: Default::default(), |
| } |
| } |
| |
| pub fn index(&self) -> u32 { |
| self.buffer.index |
| } |
| |
| pub fn queue(&self) -> QueueType { |
| QueueType::n(self.buffer.type_).unwrap() |
| } |
| |
| pub fn memory(&self) -> MemoryType { |
| MemoryType::n(self.buffer.memory).unwrap() |
| } |
| |
| /// Returns the currently set flags for this buffer. |
| pub fn flags(&self) -> BufferFlags { |
| BufferFlags::from_bits_truncate(self.buffer.flags) |
| } |
| |
| /// Sets the flags of this buffer. |
| pub fn set_flags(&mut self, flags: BufferFlags) { |
| self.buffer.flags = flags.bits(); |
| } |
| |
| /// Add `flags` to the set of flags for this buffer. |
| pub fn add_flags(&mut self, flags: BufferFlags) { |
| self.set_flags(self.flags() | flags); |
| } |
| |
| /// Remove `flags` from the set of flags for this buffer. |
| pub fn clear_flags(&mut self, flags: BufferFlags) { |
| self.set_flags(self.flags() - flags); |
| } |
| |
| pub fn field(&self) -> BufferField { |
| BufferField::n(self.buffer.field).unwrap() |
| } |
| |
| pub fn set_field(&mut self, field: BufferField) { |
| self.buffer.field = field as u32; |
| } |
| |
| pub fn is_last(&self) -> bool { |
| self.flags().contains(BufferFlags::LAST) |
| } |
| |
| pub fn timestamp(&self) -> bindings::timeval { |
| self.buffer.timestamp |
| } |
| |
| pub fn set_timestamp(&mut self, timestamp: bindings::timeval) { |
| self.buffer.timestamp = timestamp; |
| } |
| |
| pub fn sequence(&self) -> u32 { |
| self.buffer.sequence |
| } |
| |
| pub fn set_sequence(&mut self, sequence: u32) { |
| self.buffer.sequence = sequence; |
| } |
| |
| pub fn num_planes(&self) -> usize { |
| if self.queue().is_multiplanar() { |
| self.buffer.length as usize |
| } else { |
| 1 |
| } |
| } |
| |
| /// Sets the number of planes for this buffer to `num_planes`, which must be between `1` and |
| /// `VIDEO_MAX_PLANES`. |
| /// |
| /// This method only makes sense for multi-planar buffers. For single-planar buffers, any |
| /// `num_planes` value different from `1` will return an error. |
| pub fn set_num_planes(&mut self, num_planes: usize) -> Result<(), V4l2BufferResizePlanesError> { |
| match (num_planes, self.queue().is_multiplanar()) { |
| (0, _) => Err(V4l2BufferResizePlanesError::ZeroPlanesRequested), |
| (n, _) if n > bindings::VIDEO_MAX_PLANES as usize => { |
| Err(V4l2BufferResizePlanesError::TooManyPlanes) |
| } |
| (1, false) => Ok(()), |
| (_, false) => Err(V4l2BufferResizePlanesError::SinglePlanar), |
| (num_planes, true) => { |
| // If we are sizing down, clear the planes we are removing. |
| for plane in &mut self.planes[num_planes..self.buffer.length as usize] { |
| *plane = Default::default(); |
| } |
| self.buffer.length = num_planes as u32; |
| Ok(()) |
| } |
| } |
| } |
| |
| /// Returns the first plane of the buffer. This method is guaranteed to |
| /// succeed because every buffer has at least one plane. |
| pub fn get_first_plane(&self) -> V4l2PlaneAccessor { |
| self.planes_iter().next().unwrap() |
| } |
| |
| /// Returns the first plane of the buffer. This method is guaranteed to |
| /// succeed because every buffer has at least one plane. |
| pub fn get_first_plane_mut(&mut self) -> V4l2PlaneMutAccessor { |
| self.planes_iter_mut().next().unwrap() |
| } |
| |
| /// Returns a non-mutable reference to the internal `v4l2_buffer`. |
| /// |
| /// The returned value is not suitable for passing to C functions or ioctls (which anyway |
| /// require a mutable pointer), but can be useful to construct other values. |
| /// |
| /// In particular, if the buffer is multi-planar, then the `planes` pointer will be invalid. |
| /// Dereferencing it would require an `unsafe` block anyway. |
| /// |
| /// If you need to access the `v4l2_planes` of this buffer, use `as_v4l2_planes`. |
| /// |
| /// If you need to pass the `v4l2_buffer` to a C function or ioctl and need a valid `planes` |
| /// pointer, use `as_mut_ptr` and read the warning in its documentation. |
| pub fn as_v4l2_buffer(&self) -> &bindings::v4l2_buffer { |
| &self.buffer |
| } |
| |
| /// Returns a slice of this buffer's `v4l2_plane`s, if the buffer is multi-planar. |
| /// |
| /// If it is single-planar, an empty slice is returned. |
| /// |
| /// This method only exists for the rare case when one needs to access the original plane data. |
| /// For this reason there is no `v4l2_planes_mut` - use of the `planes_iter*_mut` methods |
| /// instead if you need to modify plane information. |
| pub fn as_v4l2_planes(&self) -> &[bindings::v4l2_plane] { |
| let planes_upper = if self.queue().is_multiplanar() { |
| self.buffer.length as usize |
| } else { |
| 0 |
| }; |
| |
| &self.planes[0..planes_upper] |
| } |
| |
| /// Returns a pointer to the internal `v4l2_buffer`. |
| /// |
| /// If this buffer is multi-planar then the `planes` pointer will be updated so the returned |
| /// data is valid if passed to a C function or an ioctl. |
| /// |
| /// Beware that as a consequence the returned pointer is only valid as long as the `V4l2Buffer` |
| /// is not moved anywhere. |
| /// |
| /// Also, any unsafe code called on this pointer must maintain the invariants listed in |
| /// [`V4l2Buffer`]'s documentation. |
| /// |
| /// Use with extreme caution. |
| pub fn as_mut_ptr(&mut self) -> *mut bindings::v4l2_buffer { |
| if self.queue().is_multiplanar() && self.buffer.length > 0 { |
| self.buffer.m.planes = self.planes.as_mut_ptr(); |
| } |
| |
| &mut self.buffer as *mut _ |
| } |
| |
| /// Returns planar information in a way that is consistent between single-planar and |
| /// multi-planar buffers. |
| pub fn planes_iter(&self) -> impl Iterator<Item = V4l2PlaneAccessor> { |
| let multiplanar = self.queue().is_multiplanar(); |
| let planes_iter = self.as_v4l2_planes().iter(); |
| |
| // In order to return a consistent type for both single-planar and multi-planar buffers, |
| // we chain the single-planar iterator to the multi-planar one. If the buffer is |
| // single-planar, then the multi-planar iterator will be empty. If the buffer is |
| // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. |
| std::iter::once(V4l2PlaneAccessor::new_single_planar(&self.buffer)) |
| .chain(planes_iter.map(V4l2PlaneAccessor::new_multi_planar)) |
| .skip(if multiplanar { 1 } else { 0 }) |
| } |
| |
| /// Returns planar information in a way that is consistent between single-planar and |
| /// multi-planar buffers. |
| pub fn planes_iter_mut(&mut self) -> impl Iterator<Item = V4l2PlaneMutAccessor> { |
| let multiplanar = self.queue().is_multiplanar(); |
| let planes_upper = if multiplanar { |
| self.buffer.length as usize |
| } else { |
| 0 |
| }; |
| let planes_iter = self.planes[0..planes_upper].iter_mut(); |
| |
| // In order to return a consistent type for both single-planar and multi-planar buffers, |
| // we chain the single-planar iterator to the multi-planar one. If the buffer is |
| // single-planar, then the multi-planar iterator will be empty. If the buffer is |
| // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. |
| std::iter::once(V4l2PlaneMutAccessor::new_single_planar(&mut self.buffer)) |
| .chain(planes_iter.map(V4l2PlaneMutAccessor::new_multi_planar)) |
| .skip(if multiplanar { 1 } else { 0 }) |
| } |
| |
| /// Build a plane iterator including the memory backings for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// The caller must be sure that the buffer's memory type is indeed `M`. |
| unsafe fn planes_iter_with_backing<M: Memory>( |
| &self, |
| ) -> impl Iterator<Item = V4l2PlaneAccessorWithRawBacking<M>> { |
| let is_multiplanar = self.queue().is_multiplanar(); |
| let planes_length = if is_multiplanar { |
| self.buffer.length as usize |
| } else { |
| 0 |
| }; |
| let planes = &self.planes[0..planes_length]; |
| // In order to return a consistent type for both single-planar and multi-planar buffers, |
| // we chain the single-planar iterator to the multi-planar one. If the buffer is |
| // single-planar, then the multi-planar iterator will be empty. If the buffer is |
| // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. |
| std::iter::once(V4l2PlaneAccessorWithRawBacking::new_single_planar( |
| &self.buffer, |
| )) |
| .chain( |
| planes |
| .iter() |
| .map(|p| V4l2PlaneAccessorWithRawBacking::new_multi_planar(p)), |
| ) |
| .skip(if self.queue().is_multiplanar() { 1 } else { 0 }) |
| } |
| |
| pub fn planes_with_backing_iter( |
| &self, |
| ) -> V4l2PlanesWithBacking< |
| impl Iterator<Item = V4l2PlaneAccessorWithRawBacking<Mmap>>, |
| impl Iterator<Item = V4l2PlaneAccessorWithRawBacking<UserPtr>>, |
| impl Iterator<Item = V4l2PlaneAccessorWithRawBacking<DmaBuf>>, |
| > { |
| match self.memory() { |
| MemoryType::Mmap => { |
| V4l2PlanesWithBacking::Mmap(unsafe { self.planes_iter_with_backing() }) |
| } |
| MemoryType::UserPtr => { |
| V4l2PlanesWithBacking::UserPtr(unsafe { self.planes_iter_with_backing() }) |
| } |
| MemoryType::DmaBuf => { |
| V4l2PlanesWithBacking::DmaBuf(unsafe { self.planes_iter_with_backing() }) |
| } |
| MemoryType::Overlay => V4l2PlanesWithBacking::Overlay, |
| } |
| } |
| |
| /// Build a mutable plane iterator including the memory backings for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// The caller must be sure that the buffer's memory type is indeed `M`. |
| unsafe fn planes_iter_with_backing_mut<M: Memory>( |
| &mut self, |
| ) -> impl Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<M>> { |
| let is_multiplanar = self.queue().is_multiplanar(); |
| let planes_length = if is_multiplanar { |
| self.buffer.length as usize |
| } else { |
| 0 |
| }; |
| let planes = &mut self.planes[0..planes_length]; |
| |
| // In order to return a consistent type for both single-planar and multi-planar buffers, |
| // we chain the single-planar iterator to the multi-planar one. If the buffer is |
| // single-planar, then the multi-planar iterator will be empty. If the buffer is |
| // multi-planar, we skip the first entry which is the (invalid) single-planar iterator. |
| std::iter::once(V4l2PlaneMutAccessorWithRawBacking::new_single_planar( |
| &mut self.buffer, |
| )) |
| .chain( |
| planes |
| .iter_mut() |
| .map(|p| V4l2PlaneMutAccessorWithRawBacking::new_multi_planar(p)), |
| ) |
| .skip(if is_multiplanar { 1 } else { 0 }) |
| } |
| |
| pub fn planes_with_backing_iter_mut( |
| &mut self, |
| ) -> V4l2PlanesWithBackingMut< |
| impl Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<Mmap>>, |
| impl Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<UserPtr>>, |
| impl Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<DmaBuf>>, |
| > { |
| match self.memory() { |
| MemoryType::Mmap => { |
| V4l2PlanesWithBackingMut::Mmap(unsafe { self.planes_iter_with_backing_mut() }) |
| } |
| MemoryType::UserPtr => { |
| V4l2PlanesWithBackingMut::UserPtr(unsafe { self.planes_iter_with_backing_mut() }) |
| } |
| MemoryType::DmaBuf => { |
| V4l2PlanesWithBackingMut::DmaBuf(unsafe { self.planes_iter_with_backing_mut() }) |
| } |
| MemoryType::Overlay => V4l2PlanesWithBackingMut::Overlay, |
| } |
| } |
| } |
| |
| /// Accessor to a buffer's plane information. |
| /// |
| /// This is just a set of references, that are set to point to the right location depending on |
| /// whether the buffer is single or multi-planar. |
| pub struct V4l2PlaneAccessor<'a> { |
| pub bytesused: &'a u32, |
| pub length: &'a u32, |
| pub data_offset: Option<&'a u32>, |
| } |
| |
| impl<'a> V4l2PlaneAccessor<'a> { |
| fn new_single_planar(buffer: &'a bindings::v4l2_buffer) -> Self { |
| Self { |
| bytesused: &buffer.bytesused, |
| length: &buffer.length, |
| data_offset: None, |
| } |
| } |
| |
| fn new_multi_planar(plane: &'a bindings::v4l2_plane) -> Self { |
| Self { |
| bytesused: &plane.bytesused, |
| length: &plane.length, |
| data_offset: Some(&plane.data_offset), |
| } |
| } |
| } |
| |
| /// Mutable accessor to a buffer's plane information. |
| /// |
| /// This is just a set of references, that are set to point to the right location depending on |
| /// whether the buffer is single or multi-planar. |
| pub struct V4l2PlaneMutAccessor<'a> { |
| pub bytesused: &'a mut u32, |
| pub length: &'a mut u32, |
| pub data_offset: Option<&'a mut u32>, |
| } |
| |
| impl<'a> V4l2PlaneMutAccessor<'a> { |
| fn new_single_planar(buffer: &'a mut bindings::v4l2_buffer) -> Self { |
| Self { |
| bytesused: &mut buffer.bytesused, |
| length: &mut buffer.length, |
| data_offset: None, |
| } |
| } |
| |
| fn new_multi_planar(plane: &'a mut bindings::v4l2_plane) -> Self { |
| Self { |
| bytesused: &mut plane.bytesused, |
| length: &mut plane.length, |
| data_offset: Some(&mut plane.data_offset), |
| } |
| } |
| } |
| |
| pub struct V4l2PlaneAccessorWithRawBacking<'a, M: Memory> { |
| data: V4l2PlaneAccessor<'a>, |
| backing: &'a M::RawBacking, |
| } |
| |
| impl<'a, M: Memory> Deref for V4l2PlaneAccessorWithRawBacking<'a, M> { |
| type Target = V4l2PlaneAccessor<'a>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.data |
| } |
| } |
| |
| impl<'a, M: Memory> V4l2PlaneAccessorWithRawBacking<'a, M> { |
| /// Create a new plane accessor for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// `v4l2_buffer` must be of a single-planar type and use memory type `M`. |
| pub unsafe fn new_single_planar(buffer: &'a bindings::v4l2_buffer) -> Self { |
| Self { |
| data: V4l2PlaneAccessor::new_single_planar(buffer), |
| backing: M::get_single_planar_buffer_backing(&buffer.m), |
| } |
| } |
| |
| /// Create a new plane accessor for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// `v4l2_plane` must come from a multi-planar buffer using memory type `M`. |
| pub unsafe fn new_multi_planar(plane: &'a bindings::v4l2_plane) -> Self { |
| Self { |
| data: V4l2PlaneAccessor::new_multi_planar(plane), |
| backing: M::get_plane_buffer_backing(&plane.m), |
| } |
| } |
| } |
| |
| impl<'a> V4l2PlaneAccessorWithRawBacking<'a, Mmap> { |
| pub fn mem_offset(&self) -> <Mmap as Memory>::RawBacking { |
| *self.backing |
| } |
| } |
| |
| impl<'a> V4l2PlaneAccessorWithRawBacking<'a, UserPtr> { |
| pub fn userptr(&self) -> <UserPtr as Memory>::RawBacking { |
| *self.backing |
| } |
| } |
| |
| impl<'a> V4l2PlaneAccessorWithRawBacking<'a, DmaBuf> { |
| pub fn fd(&self) -> <DmaBuf as Memory>::RawBacking { |
| *self.backing |
| } |
| } |
| |
| pub enum V4l2PlanesWithBacking< |
| 'a, |
| M: Iterator<Item = V4l2PlaneAccessorWithRawBacking<'a, Mmap>>, |
| U: Iterator<Item = V4l2PlaneAccessorWithRawBacking<'a, UserPtr>>, |
| D: Iterator<Item = V4l2PlaneAccessorWithRawBacking<'a, DmaBuf>>, |
| > { |
| Mmap(M), |
| UserPtr(U), |
| DmaBuf(D), |
| Overlay, |
| } |
| |
| pub struct V4l2PlaneMutAccessorWithRawBacking<'a, M: Memory> { |
| data: V4l2PlaneMutAccessor<'a>, |
| backing: &'a mut M::RawBacking, |
| } |
| |
| impl<'a, M: Memory> Deref for V4l2PlaneMutAccessorWithRawBacking<'a, M> { |
| type Target = V4l2PlaneMutAccessor<'a>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.data |
| } |
| } |
| |
| impl<'a, M: Memory> DerefMut for V4l2PlaneMutAccessorWithRawBacking<'a, M> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.data |
| } |
| } |
| |
| impl<'a, M: Memory> V4l2PlaneMutAccessorWithRawBacking<'a, M> { |
| /// Create a new plane accessor for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// `v4l2_buffer` must be of a single-planar type and use memory type `M`. |
| pub unsafe fn new_single_planar(buffer: &'a mut bindings::v4l2_buffer) -> Self { |
| Self { |
| data: V4l2PlaneMutAccessor { |
| bytesused: &mut buffer.bytesused, |
| length: &mut buffer.length, |
| data_offset: None, |
| }, |
| backing: M::get_single_planar_buffer_backing_mut(&mut buffer.m), |
| } |
| } |
| |
| /// Create a new plane accessor for memory type `M`. |
| /// |
| /// # Safety |
| /// |
| /// `v4l2_plane` must come from a multi-planar buffer using memory type `M`. |
| pub unsafe fn new_multi_planar(plane: &'a mut bindings::v4l2_plane) -> Self { |
| Self { |
| data: V4l2PlaneMutAccessor { |
| bytesused: &mut plane.bytesused, |
| length: &mut plane.length, |
| data_offset: Some(&mut plane.data_offset), |
| }, |
| backing: M::get_plane_buffer_backing_mut(&mut plane.m), |
| } |
| } |
| } |
| |
| impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, Mmap> { |
| pub fn mem_offset(&self) -> <Mmap as Memory>::RawBacking { |
| *self.backing |
| } |
| |
| pub fn set_mem_offset(&mut self, mem_offset: <Mmap as Memory>::RawBacking) { |
| *self.backing = mem_offset; |
| } |
| } |
| |
| impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, UserPtr> { |
| pub fn userptr(&self) -> <UserPtr as Memory>::RawBacking { |
| *self.backing |
| } |
| |
| pub fn set_userptr(&mut self, userptr: <UserPtr as Memory>::RawBacking) { |
| *self.backing = userptr; |
| } |
| } |
| |
| impl<'a> V4l2PlaneMutAccessorWithRawBacking<'a, DmaBuf> { |
| pub fn fd(&self) -> <DmaBuf as Memory>::RawBacking { |
| *self.backing |
| } |
| |
| pub fn set_fd(&mut self, fd: <DmaBuf as Memory>::RawBacking) { |
| *self.backing = fd; |
| } |
| } |
| |
| pub enum V4l2PlanesWithBackingMut< |
| 'a, |
| M: Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<'a, Mmap>>, |
| U: Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<'a, UserPtr>>, |
| D: Iterator<Item = V4l2PlaneMutAccessorWithRawBacking<'a, DmaBuf>>, |
| > { |
| Mmap(M), |
| UserPtr(U), |
| DmaBuf(D), |
| Overlay, |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum V4l2BufferFromError { |
| #[error("unknown queue type {0}")] |
| UnknownQueueType(u32), |
| #[error("unknown memory type {0}")] |
| UnknownMemoryType(u32), |
| } |
| |
| impl TryFrom<UncheckedV4l2Buffer> for V4l2Buffer { |
| type Error = V4l2BufferFromError; |
| |
| /// Do some consistency checks to ensure methods of `V4l2Buffer` that do an `unwrap` can never |
| /// fail. |
| fn try_from(buffer: UncheckedV4l2Buffer) -> Result<Self, Self::Error> { |
| let v4l2_buf = buffer.0; |
| let v4l2_planes = buffer.1; |
| QueueType::n(v4l2_buf.type_) |
| .ok_or(V4l2BufferFromError::UnknownQueueType(v4l2_buf.type_))?; |
| MemoryType::n(v4l2_buf.memory) |
| .ok_or(V4l2BufferFromError::UnknownMemoryType(v4l2_buf.memory))?; |
| |
| Ok(Self { |
| buffer: v4l2_buf, |
| planes: v4l2_planes.unwrap_or_default(), |
| }) |
| } |
| } |
| |
| /// Representation of a validated multi-planar `struct v4l2_format`. It provides accessors returning proper |
| /// types instead of `u32`s. |
| #[derive(Clone)] |
| #[repr(transparent)] |
| pub struct V4l2MplaneFormat(bindings::v4l2_format); |
| |
| impl AsRef<bindings::v4l2_format> for V4l2MplaneFormat { |
| fn as_ref(&self) -> &bindings::v4l2_format { |
| &self.0 |
| } |
| } |
| |
| impl AsRef<bindings::v4l2_pix_format_mplane> for V4l2MplaneFormat { |
| fn as_ref(&self) -> &bindings::v4l2_pix_format_mplane { |
| // SAFETY: safe because we verify that the format is pixel multiplanar at construction |
| // time. |
| unsafe { &self.0.fmt.pix_mp } |
| } |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum V4l2MplaneFormatFromError { |
| #[error("format is not multi-planar")] |
| NotMultiPlanar, |
| #[error("invalid field type {0}")] |
| InvalidField(u32), |
| #[error("invalid colorspace {0}")] |
| InvalidColorSpace(u32), |
| #[error("invalid number of planes {0}")] |
| InvalidPlanesNumber(u8), |
| #[error("invalid YCbCr encoding {0}")] |
| InvalidYCbCr(u8), |
| #[error("invalid quantization {0}")] |
| InvalidQuantization(u8), |
| #[error("invalid Xfer func {0}")] |
| InvalidXferFunc(u8), |
| } |
| |
| /// Turn a `struct v4l2_format` into its validated version, returning an error if any of the fields |
| /// cannot be validated. |
| impl TryFrom<bindings::v4l2_format> for V4l2MplaneFormat { |
| type Error = V4l2MplaneFormatFromError; |
| |
| fn try_from(format: bindings::v4l2_format) -> Result<Self, Self::Error> { |
| if !matches!( |
| QueueType::n(format.type_), |
| Some(QueueType::VideoCaptureMplane) | Some(QueueType::VideoOutputMplane) |
| ) { |
| return Err(V4l2MplaneFormatFromError::NotMultiPlanar); |
| } |
| let pix_mp = unsafe { &format.fmt.pix_mp }; |
| |
| if pix_mp.num_planes == 0 || pix_mp.num_planes > bindings::VIDEO_MAX_PLANES as u8 { |
| return Err(V4l2MplaneFormatFromError::InvalidPlanesNumber( |
| pix_mp.num_planes, |
| )); |
| } |
| |
| let _ = BufferField::n(pix_mp.field) |
| .ok_or(V4l2MplaneFormatFromError::InvalidField(pix_mp.field))?; |
| let _ = Colorspace::n(pix_mp.colorspace).ok_or( |
| V4l2MplaneFormatFromError::InvalidColorSpace(pix_mp.colorspace), |
| )?; |
| let ycbcr_enc = unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc }; |
| let _ = YCbCrEncoding::n(ycbcr_enc as u32) |
| .ok_or(V4l2MplaneFormatFromError::InvalidYCbCr(ycbcr_enc)); |
| |
| let _ = Quantization::n(pix_mp.quantization as u32).ok_or( |
| V4l2MplaneFormatFromError::InvalidQuantization(pix_mp.quantization), |
| )?; |
| let _ = XferFunc::n(pix_mp.xfer_func as u32) |
| .ok_or(V4l2MplaneFormatFromError::InvalidXferFunc(pix_mp.xfer_func))?; |
| |
| Ok(Self(format)) |
| } |
| } |
| |
| /// Turn a `struct v4l2_pix_format_mplane` into its validated version, turning any field that can |
| /// not be validated into its default value. |
| impl From<(QueueDirection, bindings::v4l2_pix_format_mplane)> for V4l2MplaneFormat { |
| fn from((direction, mut pix_mp): (QueueDirection, bindings::v4l2_pix_format_mplane)) -> Self { |
| pix_mp.field = BufferField::n(pix_mp.field).unwrap_or_default() as u32; |
| pix_mp.colorspace = Colorspace::n(pix_mp.colorspace).unwrap_or_default() as u32; |
| let ycbcr_enc = unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc }; |
| pix_mp.__bindgen_anon_1.ycbcr_enc = |
| YCbCrEncoding::n(ycbcr_enc as u32).unwrap_or_default() as u8; |
| pix_mp.quantization = Quantization::n(pix_mp.quantization as u32).unwrap_or_default() as u8; |
| pix_mp.xfer_func = XferFunc::n(pix_mp.xfer_func as u32).unwrap_or_default() as u8; |
| |
| Self(bindings::v4l2_format { |
| type_: QueueType::from_dir_and_class(direction, crate::QueueClass::VideoMplane) as u32, |
| fmt: bindings::v4l2_format__bindgen_ty_1 { pix_mp }, |
| }) |
| } |
| } |
| |
| impl V4l2MplaneFormat { |
| /// Returns the direction of the MPLANE queue this format applies to. |
| pub fn direction(&self) -> QueueDirection { |
| QueueType::n(self.0.type_).unwrap().direction() |
| } |
| |
| pub fn size(&self) -> (u32, u32) { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| (pix_mp.width, pix_mp.height) |
| } |
| |
| pub fn pixelformat(&self) -> PixelFormat { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| PixelFormat::from_u32(pix_mp.pixelformat) |
| } |
| |
| pub fn field(&self) -> BufferField { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| // Safe because we checked the boundaries at construction time. |
| BufferField::n(pix_mp.field).unwrap() |
| } |
| |
| pub fn colorspace(&self) -> Colorspace { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| // Safe because we checked the boundaries at construction time. |
| Colorspace::n(pix_mp.colorspace).unwrap() |
| } |
| |
| pub fn ycbcr_enc(&self) -> YCbCrEncoding { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| // Safe because we checked the boundaries at construction time. |
| YCbCrEncoding::n(unsafe { pix_mp.__bindgen_anon_1.ycbcr_enc as u32 }).unwrap() |
| } |
| |
| pub fn quantization(&self) -> Quantization { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| Quantization::n(pix_mp.quantization as u32).unwrap() |
| } |
| |
| pub fn xfer_func(&self) -> XferFunc { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| XferFunc::n(pix_mp.xfer_func as u32).unwrap() |
| } |
| |
| pub fn planes(&self) -> &[bindings::v4l2_plane_pix_format] { |
| let pix_mp: &bindings::v4l2_pix_format_mplane = self.as_ref(); |
| &pix_mp.plane_fmt[0..pix_mp.num_planes.min(bindings::VIDEO_MAX_PLANES as u8) as usize] |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::{bindings, QueueType}; |
| |
| use super::UncheckedV4l2Buffer; |
| |
| #[test] |
| fn test_string_from_cstr() { |
| use super::string_from_cstr; |
| |
| // Nul-terminated slice. |
| assert_eq!(string_from_cstr(b"Hello\0"), Ok(String::from("Hello"))); |
| |
| // Slice with nul in the middle and not nul-terminated. |
| assert_eq!(string_from_cstr(b"Hi\0lo"), Ok(String::from("Hi"))); |
| |
| // Slice with nul in the middle and nul-terminated. |
| assert_eq!(string_from_cstr(b"Hi\0lo\0"), Ok(String::from("Hi"))); |
| |
| // Slice starting with nul. |
| assert_eq!(string_from_cstr(b"\0ello"), Ok(String::from(""))); |
| |
| // Slice without nul. |
| match string_from_cstr(b"Hello") { |
| Err(_) => {} |
| Ok(_) => panic!(), |
| }; |
| |
| // Empty slice. |
| match string_from_cstr(b"") { |
| Err(_) => {} |
| Ok(_) => panic!(), |
| }; |
| } |
| |
| #[test] |
| fn test_unchecked_v4l2_buffer() { |
| // Single-planar. |
| let mut v4l2_buf = UncheckedV4l2Buffer::new_for_querybuf(QueueType::VideoCapture, Some(2)); |
| assert_eq!(v4l2_buf.0.type_, QueueType::VideoCapture as u32); |
| assert_eq!(v4l2_buf.0.index, 2); |
| assert_eq!(v4l2_buf.0.length, 0); |
| assert!(v4l2_buf.1.is_none()); |
| assert_eq!(unsafe { v4l2_buf.as_mut().m.planes }, std::ptr::null_mut()); |
| |
| // Multi-planar. |
| let mut v4l2_buf = |
| UncheckedV4l2Buffer::new_for_querybuf(QueueType::VideoCaptureMplane, None); |
| assert_eq!(v4l2_buf.0.type_, QueueType::VideoCaptureMplane as u32); |
| assert_eq!(v4l2_buf.0.index, 0); |
| assert_eq!(v4l2_buf.0.length, bindings::VIDEO_MAX_PLANES); |
| assert!(v4l2_buf.1.is_some()); |
| let planes_ptr = v4l2_buf.1.as_mut().map(|p| p.as_mut_ptr()).unwrap(); |
| let v4l2_buf_ref = v4l2_buf.as_mut(); |
| assert_eq!(unsafe { v4l2_buf_ref.m.planes }, planes_ptr); |
| } |
| } |