| //! Safe wrapper for the `VIDIOC_(G|S|TRY)_FMT` ioctls. |
| use crate::{bindings, FormatConversionError}; |
| use crate::{Format, PlaneLayout, QueueType}; |
| use nix::errno::Errno; |
| use std::convert::{From, Into, TryFrom, TryInto}; |
| use std::default::Default; |
| use std::mem; |
| use std::os::unix::io::AsRawFd; |
| use thiserror::Error; |
| |
| impl TryFrom<(QueueType, &Format)> for bindings::v4l2_format { |
| type Error = FormatConversionError; |
| |
| fn try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error> { |
| Ok(bindings::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(), |
| ..unsafe { mem::zeroed() } |
| }; |
| |
| 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, |
| ..unsafe { mem::zeroed() } |
| } |
| }, |
| }, |
| }, |
| }) |
| } |
| } |
| |
| // We cannot derive from the bindings since they are generated. |
| #[allow(clippy::derivable_impls)] |
| impl Default for bindings::v4l2_plane_pix_format { |
| fn default() -> Self { |
| // Safe because C does it and this results in a valid format. |
| unsafe { mem::zeroed() } |
| } |
| } |
| |
| 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<bindings::v4l2_format>>( |
| fd: &impl AsRawFd, |
| queue: QueueType, |
| ) -> Result<O, GFmtError> { |
| let mut fmt = bindings::v4l2_format { |
| type_: queue as u32, |
| ..unsafe { mem::zeroed() } |
| }; |
| |
| 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<bindings::v4l2_format>, O: TryFrom<bindings::v4l2_format>>( |
| fd: &mut impl AsRawFd, |
| format: I, |
| ) -> Result<O, SFmtError> { |
| let mut fmt: bindings::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<bindings::v4l2_format>, O: TryFrom<bindings::v4l2_format>>( |
| fd: &impl AsRawFd, |
| format: I, |
| ) -> Result<O, TryFmtError> { |
| let mut fmt: bindings::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 = bindings::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 = bindings::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::<bindings::v4l2_format>::try_into((QueueType::VideoCapture, &mplane)).err(), |
| Some(FormatConversionError::TooManyPlanes(3)) |
| ); |
| } |
| } |