1 //! Safe wrapper for the `VIDIOC_(G|S|TRY)_FMT` ioctls.
2 use nix::errno::Errno;
3 use std::convert::{From, Into, TryFrom, TryInto};
4 use std::default::Default;
5 use std::os::unix::io::AsRawFd;
6 use thiserror::Error;
7 
8 use crate::bindings;
9 use crate::bindings::v4l2_format;
10 use crate::Format;
11 use crate::FormatConversionError;
12 use crate::PlaneLayout;
13 use crate::QueueType;
14 
15 impl TryFrom<(QueueType, &Format)> for v4l2_format {
16     type Error = FormatConversionError;
17 
try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error>18     fn try_from((queue, format): (QueueType, &Format)) -> Result<Self, Self::Error> {
19         Ok(v4l2_format {
20             type_: queue as u32,
21             fmt: match queue {
22                 QueueType::VideoCaptureMplane | QueueType::VideoOutputMplane => {
23                     bindings::v4l2_format__bindgen_ty_1 {
24                         pix_mp: {
25                             if format.plane_fmt.len() > bindings::VIDEO_MAX_PLANES as usize {
26                                 return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
27                             }
28 
29                             let mut pix_mp = bindings::v4l2_pix_format_mplane {
30                                 width: format.width,
31                                 height: format.height,
32                                 pixelformat: format.pixelformat.into(),
33                                 num_planes: format.plane_fmt.len() as u8,
34                                 plane_fmt: Default::default(),
35                                 ..Default::default()
36                             };
37 
38                             for (plane, v4l2_plane) in
39                                 format.plane_fmt.iter().zip(pix_mp.plane_fmt.iter_mut())
40                             {
41                                 *v4l2_plane = plane.into();
42                             }
43 
44                             pix_mp
45                         },
46                     }
47                 }
48                 _ => bindings::v4l2_format__bindgen_ty_1 {
49                     pix: {
50                         if format.plane_fmt.len() > 1 {
51                             return Err(Self::Error::TooManyPlanes(format.plane_fmt.len()));
52                         }
53 
54                         let (bytesperline, sizeimage) = if !format.plane_fmt.is_empty() {
55                             (
56                                 format.plane_fmt[0].bytesperline,
57                                 format.plane_fmt[0].sizeimage,
58                             )
59                         } else {
60                             Default::default()
61                         };
62 
63                         bindings::v4l2_pix_format {
64                             width: format.width,
65                             height: format.height,
66                             pixelformat: format.pixelformat.into(),
67                             bytesperline,
68                             sizeimage,
69                             ..Default::default()
70                         }
71                     },
72                 },
73             },
74         })
75     }
76 }
77 
78 impl From<&PlaneLayout> for bindings::v4l2_plane_pix_format {
from(plane: &PlaneLayout) -> Self79     fn from(plane: &PlaneLayout) -> Self {
80         bindings::v4l2_plane_pix_format {
81             sizeimage: plane.sizeimage,
82             bytesperline: plane.bytesperline,
83             ..Default::default()
84         }
85     }
86 }
87 
88 #[doc(hidden)]
89 mod ioctl {
90     use crate::bindings::v4l2_format;
91     nix::ioctl_readwrite!(vidioc_g_fmt, b'V', 4, v4l2_format);
92     nix::ioctl_readwrite!(vidioc_s_fmt, b'V', 5, v4l2_format);
93     nix::ioctl_readwrite!(vidioc_try_fmt, b'V', 64, v4l2_format);
94 }
95 
96 #[derive(Debug, Error)]
97 pub enum GFmtError {
98     #[error("error while converting from V4L2 format")]
99     FromV4L2FormatConversionError,
100     #[error("invalid buffer type requested")]
101     InvalidBufferType,
102     #[error("unexpected ioctl error: {0}")]
103     IoctlError(nix::Error),
104 }
105 
106 impl From<GFmtError> for Errno {
from(err: GFmtError) -> Self107     fn from(err: GFmtError) -> Self {
108         match err {
109             GFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
110             GFmtError::InvalidBufferType => Errno::EINVAL,
111             GFmtError::IoctlError(e) => e,
112         }
113     }
114 }
115 
116 /// Safe wrapper around the `VIDIOC_G_FMT` ioctl.
g_fmt<O: TryFrom<v4l2_format>>(fd: &impl AsRawFd, queue: QueueType) -> Result<O, GFmtError>117 pub fn g_fmt<O: TryFrom<v4l2_format>>(fd: &impl AsRawFd, queue: QueueType) -> Result<O, GFmtError> {
118     let mut fmt = v4l2_format {
119         type_: queue as u32,
120         ..Default::default()
121     };
122 
123     match unsafe { ioctl::vidioc_g_fmt(fd.as_raw_fd(), &mut fmt) } {
124         Ok(_) => Ok(fmt
125             .try_into()
126             .map_err(|_| GFmtError::FromV4L2FormatConversionError)?),
127         Err(Errno::EINVAL) => Err(GFmtError::InvalidBufferType),
128         Err(e) => Err(GFmtError::IoctlError(e)),
129     }
130 }
131 
132 #[derive(Debug, Error)]
133 pub enum SFmtError {
134     #[error("error while converting from V4L2 format")]
135     FromV4L2FormatConversionError,
136     #[error("error while converting to V4L2 format")]
137     ToV4L2FormatConversionError,
138     #[error("invalid buffer type requested")]
139     InvalidBufferType,
140     #[error("device currently busy")]
141     DeviceBusy,
142     #[error("ioctl error: {0}")]
143     IoctlError(nix::Error),
144 }
145 
146 impl From<SFmtError> for Errno {
from(err: SFmtError) -> Self147     fn from(err: SFmtError) -> Self {
148         match err {
149             SFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
150             SFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
151             SFmtError::InvalidBufferType => Errno::EINVAL,
152             SFmtError::DeviceBusy => Errno::EBUSY,
153             SFmtError::IoctlError(e) => e,
154         }
155     }
156 }
157 
158 /// Safe wrapper around the `VIDIOC_S_FMT` ioctl.
s_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>( fd: &mut impl AsRawFd, format: I, ) -> Result<O, SFmtError>159 pub fn s_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
160     fd: &mut impl AsRawFd,
161     format: I,
162 ) -> Result<O, SFmtError> {
163     let mut fmt: v4l2_format = format
164         .try_into()
165         .map_err(|_| SFmtError::ToV4L2FormatConversionError)?;
166 
167     match unsafe { ioctl::vidioc_s_fmt(fd.as_raw_fd(), &mut fmt) } {
168         Ok(_) => Ok(fmt
169             .try_into()
170             .map_err(|_| SFmtError::FromV4L2FormatConversionError)?),
171         Err(Errno::EINVAL) => Err(SFmtError::InvalidBufferType),
172         Err(Errno::EBUSY) => Err(SFmtError::DeviceBusy),
173         Err(e) => Err(SFmtError::IoctlError(e)),
174     }
175 }
176 
177 #[derive(Debug, Error)]
178 pub enum TryFmtError {
179     #[error("error while converting from V4L2 format")]
180     FromV4L2FormatConversionError,
181     #[error("error while converting to V4L2 format")]
182     ToV4L2FormatConversionError,
183     #[error("invalid buffer type requested")]
184     InvalidBufferType,
185     #[error("ioctl error: {0}")]
186     IoctlError(nix::Error),
187 }
188 
189 impl From<TryFmtError> for Errno {
from(err: TryFmtError) -> Self190     fn from(err: TryFmtError) -> Self {
191         match err {
192             TryFmtError::FromV4L2FormatConversionError => Errno::EINVAL,
193             TryFmtError::ToV4L2FormatConversionError => Errno::EINVAL,
194             TryFmtError::InvalidBufferType => Errno::EINVAL,
195             TryFmtError::IoctlError(e) => e,
196         }
197     }
198 }
199 
200 /// Safe wrapper around the `VIDIOC_TRY_FMT` ioctl.
try_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>( fd: &impl AsRawFd, format: I, ) -> Result<O, TryFmtError>201 pub fn try_fmt<I: TryInto<v4l2_format>, O: TryFrom<v4l2_format>>(
202     fd: &impl AsRawFd,
203     format: I,
204 ) -> Result<O, TryFmtError> {
205     let mut fmt: v4l2_format = format
206         .try_into()
207         .map_err(|_| TryFmtError::ToV4L2FormatConversionError)?;
208 
209     match unsafe { ioctl::vidioc_try_fmt(fd.as_raw_fd(), &mut fmt) } {
210         Ok(_) => Ok(fmt
211             .try_into()
212             .map_err(|_| TryFmtError::FromV4L2FormatConversionError)?),
213         Err(Errno::EINVAL) => Err(TryFmtError::InvalidBufferType),
214         Err(e) => Err(TryFmtError::IoctlError(e)),
215     }
216 }
217 
218 #[cfg(test)]
219 mod test {
220     use super::*;
221     use std::convert::TryInto;
222 
223     #[test]
224     // Convert from Format to multi-planar v4l2_format and back.
mplane_to_v4l2_format()225     fn mplane_to_v4l2_format() {
226         // This is not a real format but let us use unique values per field.
227         let mplane = Format {
228             width: 632,
229             height: 480,
230             pixelformat: b"NM12".into(),
231             plane_fmt: vec![
232                 PlaneLayout {
233                     sizeimage: 307200,
234                     bytesperline: 640,
235                 },
236                 PlaneLayout {
237                     sizeimage: 153600,
238                     bytesperline: 320,
239                 },
240                 PlaneLayout {
241                     sizeimage: 76800,
242                     bytesperline: 160,
243                 },
244             ],
245         };
246         let v4l2_format = v4l2_format {
247             ..(QueueType::VideoCaptureMplane, &mplane).try_into().unwrap()
248         };
249         let mplane2: Format = v4l2_format.try_into().unwrap();
250         assert_eq!(mplane, mplane2);
251     }
252 
253     #[test]
254     // Convert from Format to single-planar v4l2_format and back.
splane_to_v4l2_format()255     fn splane_to_v4l2_format() {
256         // This is not a real format but let us use unique values per field.
257         let splane = Format {
258             width: 632,
259             height: 480,
260             pixelformat: b"NV12".into(),
261             plane_fmt: vec![PlaneLayout {
262                 sizeimage: 307200,
263                 bytesperline: 640,
264             }],
265         };
266         // Conversion to/from single-planar format.
267         let v4l2_format = v4l2_format {
268             ..(QueueType::VideoCapture, &splane).try_into().unwrap()
269         };
270         let splane2: Format = v4l2_format.try_into().unwrap();
271         assert_eq!(splane, splane2);
272 
273         // Trying to use a multi-planar format with the single-planar API should
274         // fail.
275         let mplane = Format {
276             width: 632,
277             height: 480,
278             pixelformat: b"NM12".into(),
279             // This is not a real format but let us use unique values per field.
280             plane_fmt: vec![
281                 PlaneLayout {
282                     sizeimage: 307200,
283                     bytesperline: 640,
284                 },
285                 PlaneLayout {
286                     sizeimage: 153600,
287                     bytesperline: 320,
288                 },
289                 PlaneLayout {
290                     sizeimage: 76800,
291                     bytesperline: 160,
292                 },
293             ],
294         };
295         assert_eq!(
296             TryInto::<v4l2_format>::try_into((QueueType::VideoCapture, &mplane)).err(),
297             Some(FormatConversionError::TooManyPlanes(3))
298         );
299     }
300 }
301