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