blob: b85c9fc1f969ece23529f99079f7f87a10764a43 [file] [log] [blame]
//! Safe wrapper for the `VIDIOC_(TRY_)DECODER_CMD` ioctls.
use bitflags::bitflags;
use nix::errno::Errno;
use std::convert::Infallible;
use std::convert::TryFrom;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_decoder_cmd;
use crate::ioctl::ioctl_and_convert;
use crate::ioctl::IoctlConvertError;
use crate::ioctl::IoctlConvertResult;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StartCmdFlags: u32 {
const MUTE_AUDIO = bindings::V4L2_DEC_CMD_START_MUTE_AUDIO;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StopCmdFlags: u32 {
const TO_BLACK = bindings::V4L2_DEC_CMD_STOP_TO_BLACK;
const IMMEDIATELY = bindings::V4L2_DEC_CMD_STOP_IMMEDIATELY;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PauseCmdFlags: u32 {
const TO_BLACK = bindings::V4L2_DEC_CMD_PAUSE_TO_BLACK;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, enumn::N)]
#[repr(u32)]
pub enum DecoderStartCmdFormat {
#[default]
None = bindings::V4L2_DEC_START_FMT_NONE,
Gop = bindings::V4L2_DEC_START_FMT_GOP,
}
/// Safe variant of `struct v4l2_decoder_cmd`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecoderCmd {
Start {
flags: StartCmdFlags,
speed: i32,
format: DecoderStartCmdFormat,
},
Stop {
flags: StopCmdFlags,
pts: u64,
},
Pause {
flags: PauseCmdFlags,
},
Resume,
}
impl DecoderCmd {
/// Returns a simple START command without any extra parameter.
pub fn start() -> Self {
DecoderCmd::Start {
flags: StartCmdFlags::empty(),
speed: Default::default(),
format: Default::default(),
}
}
/// Returns a simple STOP command without any extra parameter.
pub fn stop() -> Self {
DecoderCmd::Stop {
flags: StopCmdFlags::empty(),
pts: Default::default(),
}
}
/// Returns a simple PAUSE command without any extra parameter.
pub fn pause() -> Self {
DecoderCmd::Pause {
flags: PauseCmdFlags::empty(),
}
}
/// Returns a RESUME command.
pub fn resume() -> Self {
DecoderCmd::Resume
}
}
impl From<DecoderCmd> for v4l2_decoder_cmd {
fn from(command: DecoderCmd) -> Self {
match command {
DecoderCmd::Start {
flags,
speed,
format,
} => v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_START,
flags: flags.bits(),
__bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
start: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_2 {
speed,
format: format as u32,
},
},
},
DecoderCmd::Stop { flags, pts } => v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_STOP,
flags: flags.bits(),
__bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
stop: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_1 { pts },
},
},
DecoderCmd::Pause { flags } => v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_PAUSE,
flags: flags.bits(),
__bindgen_anon_1: Default::default(),
},
DecoderCmd::Resume => v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_RESUME,
flags: Default::default(),
__bindgen_anon_1: Default::default(),
},
}
}
}
#[derive(Debug, Error)]
pub enum BuildDecoderCmdError {
#[error("invalid command code {0}")]
InvalidCommandCode(u32),
#[error("invalid start command flags 0x{0:x}")]
InvalidStartFlags(u32),
#[error("invalid start command format {0}")]
InvalidStartFormat(u32),
#[error("invalid stop command flags 0x{0:x}")]
InvalidStopFlags(u32),
#[error("invalid pause command flags 0x{0:x}")]
InvalidPauseFlags(u32),
}
impl TryFrom<v4l2_decoder_cmd> for DecoderCmd {
type Error = BuildDecoderCmdError;
fn try_from(cmd: v4l2_decoder_cmd) -> Result<Self, Self::Error> {
let cmd = match cmd.cmd {
bindings::V4L2_DEC_CMD_START => {
// SAFETY: safe because we confirmed we are dealing with a START command.
let params = unsafe { cmd.__bindgen_anon_1.start };
DecoderCmd::Start {
flags: StartCmdFlags::from_bits_truncate(cmd.flags),
speed: params.speed,
format: DecoderStartCmdFormat::n(params.format).unwrap_or_default(),
}
}
bindings::V4L2_DEC_CMD_STOP => DecoderCmd::Stop {
flags: StopCmdFlags::from_bits_truncate(cmd.flags),
// SAFETY: safe because we confirmed we are dealing with a STOP command.
pts: unsafe { cmd.__bindgen_anon_1.stop.pts },
},
bindings::V4L2_DEC_CMD_PAUSE => DecoderCmd::Pause {
flags: PauseCmdFlags::from_bits_truncate(cmd.flags),
},
bindings::V4L2_DEC_CMD_RESUME => DecoderCmd::Resume,
code => return Err(BuildDecoderCmdError::InvalidCommandCode(code)),
};
Ok(cmd)
}
}
impl TryFrom<v4l2_decoder_cmd> for () {
type Error = Infallible;
fn try_from(_: v4l2_decoder_cmd) -> Result<Self, Self::Error> {
Ok(())
}
}
#[derive(Debug, Error)]
pub enum DecoderCmdIoctlError {
#[error("drain already in progress")]
DrainInProgress,
#[error("command not supported by device")]
UnsupportedCommand,
#[error("unexpected ioctl error: {0}")]
Other(Errno),
}
impl From<DecoderCmdIoctlError> for Errno {
fn from(err: DecoderCmdIoctlError) -> Self {
match err {
DecoderCmdIoctlError::DrainInProgress => Errno::EBUSY,
DecoderCmdIoctlError::UnsupportedCommand => Errno::EINVAL,
DecoderCmdIoctlError::Other(e) => e,
}
}
}
impl From<Errno> for DecoderCmdIoctlError {
fn from(error: Errno) -> Self {
match error {
Errno::EBUSY => DecoderCmdIoctlError::DrainInProgress,
Errno::EINVAL => DecoderCmdIoctlError::UnsupportedCommand,
e => DecoderCmdIoctlError::Other(e),
}
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_decoder_cmd;
nix::ioctl_readwrite!(vidioc_decoder_cmd, b'V', 96, v4l2_decoder_cmd);
nix::ioctl_readwrite!(vidioc_try_decoder_cmd, b'V', 97, v4l2_decoder_cmd);
}
pub type DecoderCmdError<CE> = IoctlConvertError<DecoderCmdIoctlError, CE>;
pub type DecoderCmdResult<O, CE> = IoctlConvertResult<O, DecoderCmdIoctlError, CE>;
/// Safe wrapper around the `VIDIOC_DECODER_CMD` ioctl.
pub fn 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,
{
let mut dec_cmd = command.into();
ioctl_and_convert(
unsafe { ioctl::vidioc_decoder_cmd(fd.as_raw_fd(), &mut dec_cmd) }
.map(|_| dec_cmd)
.map_err(Into::into),
)
}
/// Safe wrapper around the `VIDIOC_TRY_DECODER_CMD` ioctl.
pub fn 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,
{
let mut dec_cmd = command.into();
ioctl_and_convert(
unsafe { ioctl::vidioc_try_decoder_cmd(fd.as_raw_fd(), &mut dec_cmd) }
.map(|_| dec_cmd)
.map_err(Into::into),
)
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use crate::{
bindings,
ioctl::{DecoderStartCmdFormat, PauseCmdFlags, StartCmdFlags, StopCmdFlags},
};
use super::DecoderCmd;
#[test]
fn build_decoder_cmd() {
// Build START command and back.
let cmd = bindings::v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_START,
flags: bindings::V4L2_DEC_CMD_START_MUTE_AUDIO,
__bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
start: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_2 {
speed: 42,
format: bindings::V4L2_DEC_START_FMT_GOP,
},
},
};
let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
assert_eq!(
cmd_safe,
DecoderCmd::Start {
flags: StartCmdFlags::MUTE_AUDIO,
speed: 42,
format: DecoderStartCmdFormat::Gop,
}
);
let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
// Build STOP command and back.
let cmd = bindings::v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_STOP,
flags: bindings::V4L2_DEC_CMD_STOP_IMMEDIATELY | bindings::V4L2_DEC_CMD_STOP_TO_BLACK,
__bindgen_anon_1: bindings::v4l2_decoder_cmd__bindgen_ty_1 {
stop: bindings::v4l2_decoder_cmd__bindgen_ty_1__bindgen_ty_1 { pts: 496000 },
},
};
let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
assert_eq!(
cmd_safe,
DecoderCmd::Stop {
flags: StopCmdFlags::IMMEDIATELY | StopCmdFlags::TO_BLACK,
pts: 496000,
}
);
let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
// Build PAUSE command and back.
let cmd = bindings::v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_PAUSE,
flags: bindings::V4L2_DEC_CMD_PAUSE_TO_BLACK,
__bindgen_anon_1: Default::default(),
};
let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
assert_eq!(
cmd_safe,
DecoderCmd::Pause {
flags: PauseCmdFlags::TO_BLACK,
}
);
let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
// Build RESUME command and back.
let cmd = bindings::v4l2_decoder_cmd {
cmd: bindings::V4L2_DEC_CMD_RESUME,
flags: Default::default(),
__bindgen_anon_1: Default::default(),
};
let cmd_safe = DecoderCmd::try_from(cmd).unwrap();
assert_eq!(cmd_safe, DecoderCmd::Resume);
let cmd_rebuilt: bindings::v4l2_decoder_cmd = cmd_safe.into();
assert_eq!(cmd_safe, DecoderCmd::try_from(cmd_rebuilt).unwrap());
}
}