1 //! Safe wrapper for the `VIDIOC_ENUM_FMT` ioctl.
2 use super::string_from_cstr;
3 use crate::bindings;
4 use crate::bindings::v4l2_fmtdesc;
5 use crate::{PixelFormat, QueueType};
6 use bitflags::bitflags;
7 use log::error;
8 use nix::errno::Errno;
9 use std::fmt;
10 use std::os::unix::io::AsRawFd;
11 use thiserror::Error;
12 
13 bitflags! {
14     /// Flags returned by the `VIDIOC_ENUM_FMT` ioctl into the `flags` field of
15     /// `struct v4l2_fmtdesc`.
16     #[derive(Clone, Copy, Debug)]
17     pub struct FormatFlags: u32 {
18         const COMPRESSED = bindings::V4L2_FMT_FLAG_COMPRESSED;
19         const EMULATED = bindings::V4L2_FMT_FLAG_EMULATED;
20     }
21 }
22 /// Quickly get the Fourcc code of a format.
23 impl From<v4l2_fmtdesc> for PixelFormat {
from(fmtdesc: v4l2_fmtdesc) -> Self24     fn from(fmtdesc: v4l2_fmtdesc) -> Self {
25         fmtdesc.pixelformat.into()
26     }
27 }
28 
29 /// Safe variant of the `v4l2_fmtdesc` struct, to be used with `enum_fmt`.
30 #[derive(Debug)]
31 pub struct FmtDesc {
32     pub flags: FormatFlags,
33     pub description: String,
34     pub pixelformat: PixelFormat,
35 }
36 
37 impl fmt::Display for FmtDesc {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result38     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39         write!(
40             f,
41             "{}: {} {}",
42             self.pixelformat,
43             self.description,
44             if self.flags.is_empty() {
45                 "".into()
46             } else {
47                 format!("({:?})", self.flags)
48             }
49         )
50     }
51 }
52 
53 impl From<v4l2_fmtdesc> for FmtDesc {
from(fmtdesc: v4l2_fmtdesc) -> Self54     fn from(fmtdesc: v4l2_fmtdesc) -> Self {
55         FmtDesc {
56             flags: FormatFlags::from_bits_truncate(fmtdesc.flags),
57             description: string_from_cstr(&fmtdesc.description).unwrap_or_else(|_| "".into()),
58             pixelformat: fmtdesc.pixelformat.into(),
59         }
60     }
61 }
62 
63 #[doc(hidden)]
64 mod ioctl {
65     use crate::bindings::v4l2_fmtdesc;
66     nix::ioctl_readwrite!(vidioc_enum_fmt, b'V', 2, v4l2_fmtdesc);
67 }
68 
69 #[derive(Debug, Error)]
70 pub enum EnumFmtError {
71     #[error("ioctl error: {0}")]
72     IoctlError(#[from] nix::Error),
73 }
74 
75 impl From<EnumFmtError> for Errno {
from(err: EnumFmtError) -> Self76     fn from(err: EnumFmtError) -> Self {
77         match err {
78             EnumFmtError::IoctlError(e) => e,
79         }
80     }
81 }
82 
83 /// Safe wrapper around the `VIDIOC_ENUM_FMT` ioctl.
enum_fmt<T: From<v4l2_fmtdesc>>( fd: &impl AsRawFd, queue: QueueType, index: u32, ) -> Result<T, EnumFmtError>84 pub fn enum_fmt<T: From<v4l2_fmtdesc>>(
85     fd: &impl AsRawFd,
86     queue: QueueType,
87     index: u32,
88 ) -> Result<T, EnumFmtError> {
89     let mut fmtdesc = v4l2_fmtdesc {
90         type_: queue as u32,
91         index,
92         ..Default::default()
93     };
94     unsafe { ioctl::vidioc_enum_fmt(fd.as_raw_fd(), &mut fmtdesc) }?;
95 
96     Ok(T::from(fmtdesc))
97 }
98 
99 /// Iterator over the formats of the given queue. This takes a reference to the
100 /// device's file descriptor so no operation that could affect the format
101 /// enumeration can take place while the iterator exists.
102 pub struct FormatIterator<'a, F: AsRawFd> {
103     fd: &'a F,
104     queue: QueueType,
105     index: u32,
106 }
107 
108 impl<'a, F: AsRawFd> FormatIterator<'a, F> {
109     /// Create a new iterator listing all the currently valid formats on
110     /// `queue`.
new(fd: &'a F, queue: QueueType) -> Self111     pub fn new(fd: &'a F, queue: QueueType) -> Self {
112         FormatIterator {
113             fd,
114             queue,
115             index: 0,
116         }
117     }
118 }
119 
120 impl<'a, F: AsRawFd> Iterator for FormatIterator<'a, F> {
121     type Item = FmtDesc;
122 
next(&mut self) -> Option<Self::Item>123     fn next(&mut self) -> Option<Self::Item> {
124         match enum_fmt(self.fd, self.queue, self.index) {
125             Ok(fmtdesc) => {
126                 self.index += 1;
127                 Some(fmtdesc)
128             }
129             // EINVAL means we have reached the last format.
130             Err(EnumFmtError::IoctlError(Errno::EINVAL)) => None,
131             _ => {
132                 error!("Unexpected return value for VIDIOC_ENUM_FMT!");
133                 None
134             }
135         }
136     }
137 }
138