v4l2r 0.0.7

Safe and flexible abstraction over V4L2
Documentation
//! 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 std::os::unix::prelude::RawFd;
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 request: Option<RawFd>,
    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(),
            request: None,
            _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
    }

    pub fn set_request(mut self, fd: RawFd) -> Self {
        self.request = Some(fd);
        self.flags |= BufferFlags::REQUEST_FD;
        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(request) = &qbuf.request {
            v4l2_buf.0.__bindgen_anon_1.request_fd = *request;
        }
        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),
    )
}