1 //! Safe wrapper for the `VIDIOC_(TRY_)DECODER_CMD` ioctls.
2 
3 use bitflags::bitflags;
4 use nix::errno::Errno;
5 use std::convert::Infallible;
6 use std::convert::TryFrom;
7 use std::os::unix::io::AsRawFd;
8 use thiserror::Error;
9 
10 use crate::bindings;
11 use crate::bindings::v4l2_decoder_cmd;
12 use crate::ioctl::ioctl_and_convert;
13 use crate::ioctl::IoctlConvertError;
14 use crate::ioctl::IoctlConvertResult;
15 
16 bitflags! {
17     #[derive(Clone, Copy, Debug, PartialEq, Eq)]
18     pub struct StartCmdFlags: u32 {
19         const MUTE_AUDIO = bindings::V4L2_DEC_CMD_START_MUTE_AUDIO;
20     }
21 
22     #[derive(Clone, Copy, Debug, PartialEq, Eq)]
23     pub struct StopCmdFlags: u32 {
24         const TO_BLACK = bindings::V4L2_DEC_CMD_STOP_TO_BLACK;
25         const IMMEDIATELY = bindings::V4L2_DEC_CMD_STOP_IMMEDIATELY;
26     }
27 
28     #[derive(Clone, Copy, Debug, PartialEq, Eq)]
29     pub struct PauseCmdFlags: u32 {
30         const TO_BLACK = bindings::V4L2_DEC_CMD_PAUSE_TO_BLACK;
31     }
32 }
33 
34 #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, enumn::N)]
35 #[repr(u32)]
36 pub enum DecoderStartCmdFormat {
37     #[default]
38     None = bindings::V4L2_DEC_START_FMT_NONE,
39     Gop = bindings::V4L2_DEC_START_FMT_GOP,
40 }
41 
42 /// Safe variant of `struct v4l2_decoder_cmd`.
43 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44 pub enum DecoderCmd {
45     Start {
46         flags: StartCmdFlags,
47         speed: i32,
48         format: DecoderStartCmdFormat,
49     },
50     Stop {
51         flags: StopCmdFlags,
52         pts: u64,
53     },
54     Pause {
55         flags: PauseCmdFlags,
56     },
57     Resume,
58 }
59 
60 impl DecoderCmd {
61     /// Returns a simple START command without any extra parameter.
start() -> Self62     pub fn start() -> Self {
63         DecoderCmd::Start {
64             flags: StartCmdFlags::empty(),
65             speed: Default::default(),
66             format: Default::default(),
67         }
68     }
69 
70     /// Returns a simple STOP command without any extra parameter.
stop() -> Self71     pub fn stop() -> Self {
72         DecoderCmd::Stop {
73             flags: StopCmdFlags::empty(),
74             pts: Default::default(),
75         }
76     }
77 
78     /// Returns a simple PAUSE command without any extra parameter.
pause() -> Self79     pub fn pause() -> Self {
80         DecoderCmd::Pause {
81             flags: PauseCmdFlags::empty(),
82         }
83     }
84 
85     /// Returns a RESUME command.
resume() -> Self86     pub fn resume() -> Self {
87         DecoderCmd::Resume
88     }
89 }
90 
91 impl From<DecoderCmd> for v4l2_decoder_cmd {
from(command: DecoderCmd) -> Self92     fn from(command: DecoderCmd) -> Self {
93         match command {
94             DecoderCmd::Start {
95                 flags,
96                 speed,
97                 format,
98             } => v4l2_decoder_cmd {
99                 cmd: bindings::V4L2_DEC_CMD_START,
100                 flags: flags.bits(),
101                 __bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
102                     start: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_2 {
103                         speed,
104                         format: format as u32,
105                     },
106                 },
107             },
108             DecoderCmd::Stop { flags, pts } => v4l2_decoder_cmd {
109                 cmd: bindings::V4L2_DEC_CMD_STOP,
110                 flags: flags.bits(),
111                 __bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
112                     stop: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_1 { pts },
113                 },
114             },
115             DecoderCmd::Pause { flags } => v4l2_decoder_cmd {
116                 cmd: bindings::V4L2_DEC_CMD_PAUSE,
117                 flags: flags.bits(),
118                 __bindgen_anon_1: Default::default(),
119             },
120             DecoderCmd::Resume => v4l2_decoder_cmd {
121                 cmd: bindings::V4L2_DEC_CMD_RESUME,
122                 flags: Default::default(),
123                 __bindgen_anon_1: Default::default(),
124             },
125         }
126     }
127 }
128 
129 #[derive(Debug, Error)]
130 pub enum BuildDecoderCmdError {
131     #[error("invalid command code {0}")]
132     InvalidCommandCode(u32),
133     #[error("invalid start command flags 0x{0:x}")]
134     InvalidStartFlags(u32),
135     #[error("invalid start command format {0}")]
136     InvalidStartFormat(u32),
137     #[error("invalid stop command flags 0x{0:x}")]
138     InvalidStopFlags(u32),
139     #[error("invalid pause command flags 0x{0:x}")]
140     InvalidPauseFlags(u32),
141 }
142 
143 impl TryFrom<v4l2_decoder_cmd> for DecoderCmd {
144     type Error = BuildDecoderCmdError;
145 
try_from(cmd: v4l2_decoder_cmd) -> Result<Self, Self::Error>146     fn try_from(cmd: v4l2_decoder_cmd) -> Result<Self, Self::Error> {
147         let cmd = match cmd.cmd {
148             bindings::V4L2_DEC_CMD_START => {
149                 // SAFETY: safe because we confirmed we are dealing with a START command.
150                 let params = unsafe { cmd.__bindgen_anon_1.start };
151                 DecoderCmd::Start {
152                     flags: StartCmdFlags::from_bits_truncate(cmd.flags),
153                     speed: params.speed,
154                     format: DecoderStartCmdFormat::n(params.format).unwrap_or_default(),
155                 }
156             }
157             bindings::V4L2_DEC_CMD_STOP => DecoderCmd::Stop {
158                 flags: StopCmdFlags::from_bits_truncate(cmd.flags),
159                 // SAFETY: safe because we confirmed we are dealing with a STOP command.
160                 pts: unsafe { cmd.__bindgen_anon_1.stop.pts },
161             },
162             bindings::V4L2_DEC_CMD_PAUSE => DecoderCmd::Pause {
163                 flags: PauseCmdFlags::from_bits_truncate(cmd.flags),
164             },
165             bindings::V4L2_DEC_CMD_RESUME => DecoderCmd::Resume,
166             code => return Err(BuildDecoderCmdError::InvalidCommandCode(code)),
167         };
168 
169         Ok(cmd)
170     }
171 }
172 
173 impl TryFrom<v4l2_decoder_cmd> for () {
174     type Error = Infallible;
175 
try_from(_: v4l2_decoder_cmd) -> Result<Self, Self::Error>176     fn try_from(_: v4l2_decoder_cmd) -> Result<Self, Self::Error> {
177         Ok(())
178     }
179 }
180 
181 #[derive(Debug, Error)]
182 pub enum DecoderCmdIoctlError {
183     #[error("drain already in progress")]
184     DrainInProgress,
185     #[error("command not supported by device")]
186     UnsupportedCommand,
187     #[error("unexpected ioctl error: {0}")]
188     Other(Errno),
189 }
190 
191 impl From<DecoderCmdIoctlError> for Errno {
from(err: DecoderCmdIoctlError) -> Self192     fn from(err: DecoderCmdIoctlError) -> Self {
193         match err {
194             DecoderCmdIoctlError::DrainInProgress => Errno::EBUSY,
195             DecoderCmdIoctlError::UnsupportedCommand => Errno::EINVAL,
196             DecoderCmdIoctlError::Other(e) => e,
197         }
198     }
199 }
200 
201 impl From<Errno> for DecoderCmdIoctlError {
from(error: Errno) -> Self202     fn from(error: Errno) -> Self {
203         match error {
204             Errno::EBUSY => DecoderCmdIoctlError::DrainInProgress,
205             Errno::EINVAL => DecoderCmdIoctlError::UnsupportedCommand,
206             e => DecoderCmdIoctlError::Other(e),
207         }
208     }
209 }
210 
211 #[doc(hidden)]
212 mod ioctl {
213     use crate::bindings::v4l2_decoder_cmd;
214     nix::ioctl_readwrite!(vidioc_decoder_cmd, b'V', 96, v4l2_decoder_cmd);
215     nix::ioctl_readwrite!(vidioc_try_decoder_cmd, b'V', 97, v4l2_decoder_cmd);
216 }
217 
218 pub type DecoderCmdError<CE> = IoctlConvertError<DecoderCmdIoctlError, CE>;
219 pub type DecoderCmdResult<O, CE> = IoctlConvertResult<O, DecoderCmdIoctlError, CE>;
220 
221 /// Safe wrapper around the `VIDIOC_DECODER_CMD` ioctl.
decoder_cmd<I, O>(fd: &impl AsRawFd, command: I) -> DecoderCmdResult<O, O::Error> where I: Into<v4l2_decoder_cmd>, O: TryFrom<v4l2_decoder_cmd>, O::Error: std::fmt::Debug,222 pub fn decoder_cmd<I, O>(fd: &impl AsRawFd, command: I) -> DecoderCmdResult<O, O::Error>
223 where
224     I: Into<v4l2_decoder_cmd>,
225     O: TryFrom<v4l2_decoder_cmd>,
226     O::Error: std::fmt::Debug,
227 {
228     let mut dec_cmd = command.into();
229 
230     ioctl_and_convert(
231         unsafe { ioctl::vidioc_decoder_cmd(fd.as_raw_fd(), &mut dec_cmd) }
232             .map(|_| dec_cmd)
233             .map_err(Into::into),
234     )
235 }
236 
237 /// Safe wrapper around the `VIDIOC_TRY_DECODER_CMD` ioctl.
try_decoder_cmd<I, O>(fd: &impl AsRawFd, command: I) -> DecoderCmdResult<O, O::Error> where I: Into<v4l2_decoder_cmd>, O: TryFrom<v4l2_decoder_cmd>, O::Error: std::fmt::Debug,238 pub fn try_decoder_cmd<I, O>(fd: &impl AsRawFd, command: I) -> DecoderCmdResult<O, O::Error>
239 where
240     I: Into<v4l2_decoder_cmd>,
241     O: TryFrom<v4l2_decoder_cmd>,
242     O::Error: std::fmt::Debug,
243 {
244     let mut dec_cmd = command.into();
245 
246     ioctl_and_convert(
247         unsafe { ioctl::vidioc_try_decoder_cmd(fd.as_raw_fd(), &mut dec_cmd) }
248             .map(|_| dec_cmd)
249             .map_err(Into::into),
250     )
251 }
252 
253 #[cfg(test)]
254 mod tests {
255     use std::convert::TryFrom;
256 
257     use crate::{
258         bindings,
259         ioctl::{DecoderStartCmdFormat, PauseCmdFlags, StartCmdFlags, StopCmdFlags},
260     };
261 
262     use super::DecoderCmd;
263 
264     #[test]
build_decoder_cmd()265     fn build_decoder_cmd() {
266         // Build START command and back.
267         let cmd = bindings::v4l2_decoder_cmd {
268             cmd: bindings::V4L2_DEC_CMD_START,
269             flags: bindings::V4L2_DEC_CMD_START_MUTE_AUDIO,
270             __bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
271                 start: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_2 {
272                     speed: 42,
273                     format: bindings::V4L2_DEC_START_FMT_GOP,
274                 },
275             },
276         };
277         let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
278         assert_eq!(
279             cmd_safe,
280             DecoderCmd::Start {
281                 flags: StartCmdFlags::MUTE_AUDIO,
282                 speed: 42,
283                 format: DecoderStartCmdFormat::Gop,
284             }
285         );
286         let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
287         assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
288 
289         // Build STOP command and back.
290         let cmd = bindings::v4l2_decoder_cmd {
291             cmd: bindings::V4L2_DEC_CMD_STOP,
292             flags: bindings::V4L2_DEC_CMD_STOP_IMMEDIATELY | bindings::V4L2_DEC_CMD_STOP_TO_BLACK,
293             __bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
294                 stop: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_1 { pts: 496000 },
295             },
296         };
297         let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
298         assert_eq!(
299             cmd_safe,
300             DecoderCmd::Stop {
301                 flags: StopCmdFlags::IMMEDIATELY | StopCmdFlags::TO_BLACK,
302                 pts: 496000,
303             }
304         );
305         let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
306         assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
307 
308         // Build PAUSE command and back.
309         let cmd = bindings::v4l2_decoder_cmd {
310             cmd: bindings::V4L2_DEC_CMD_PAUSE,
311             flags: bindings::V4L2_DEC_CMD_PAUSE_TO_BLACK,
312             __bindgen_anon_1: Default::default(),
313         };
314         let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
315         assert_eq!(
316             cmd_safe,
317             DecoderCmd::Pause {
318                 flags: PauseCmdFlags::TO_BLACK,
319             }
320         );
321         let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
322         assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
323 
324         // Build RESUME command and back.
325         let cmd = bindings::v4l2_decoder_cmd {
326             cmd: bindings::V4L2_DEC_CMD_RESUME,
327             flags: Default::default(),
328             __bindgen_anon_1: Default::default(),
329         };
330         let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
331         assert_eq!(cmd_safe, DecoderCmd::Resume);
332         let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
333         assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
334     }
335 }
336