blob: 6c4a9a5d40945157557d6512bfb2e59a4cbda29e [file] [log] [blame]
//! 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))
);
}
}