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