v4l2r 0.0.7

Safe and flexible abstraction over V4L2
Documentation
use std::os::unix::io::AsRawFd;

use bitflags::bitflags;
use enumn::N;
use nix::errno::Errno;
use thiserror::Error;

use crate::bindings;
use crate::bindings::v4l2_audio;
use crate::bindings::v4l2_audioout;
use crate::bindings::v4l2_frequency;
use crate::bindings::v4l2_frequency_band;
use crate::bindings::v4l2_modulator;
use crate::bindings::v4l2_tuner;

bitflags! {
    #[derive(Clone, Copy, Debug)]
    pub struct AudioCapability: u32 {
        const STEREO = bindings::V4L2_AUDCAP_STEREO;
        const AVL = bindings::V4L2_AUDCAP_AVL;
    }
}

#[derive(Debug, N)]
#[repr(u32)]
pub enum AudioMode {
    Avl = bindings::V4L2_AUDMODE_AVL,
}

#[derive(Clone, Copy, Debug, N)]
#[repr(u32)]
pub enum TunerType {
    Radio = bindings::v4l2_tuner_type_V4L2_TUNER_RADIO,
    AnalogTv = bindings::v4l2_tuner_type_V4L2_TUNER_ANALOG_TV,
    DigitalTv = bindings::v4l2_tuner_type_V4L2_TUNER_DIGITAL_TV,
    Sdr = bindings::v4l2_tuner_type_V4L2_TUNER_SDR,
    Rf = bindings::v4l2_tuner_type_V4L2_TUNER_RF,
}

bitflags! {
    #[derive(Clone, Copy, Debug)]
    pub struct TunerCapFlags: u32 {
        const LOW = bindings::V4L2_TUNER_CAP_LOW;
        const NORM = bindings::V4L2_TUNER_CAP_NORM;
        const HWSEEK_BOUNDED = bindings::V4L2_TUNER_CAP_HWSEEK_BOUNDED;
        const HWSEEK_WRAP = bindings::V4L2_TUNER_CAP_HWSEEK_WRAP;
        const STEREO = bindings::V4L2_TUNER_CAP_STEREO;
        const LANG1 = bindings::V4L2_TUNER_CAP_LANG1;
        const LANG2 = bindings::V4L2_TUNER_CAP_LANG2;
        const SAP = bindings::V4L2_TUNER_CAP_SAP;
        const RDS = bindings::V4L2_TUNER_CAP_RDS;
        const RDS_BLOCK_IO = bindings::V4L2_TUNER_CAP_RDS_BLOCK_IO;
        const RDS_CONTROLS = bindings::V4L2_TUNER_CAP_RDS_CONTROLS;
        const FREQ_BANDS = bindings::V4L2_TUNER_CAP_FREQ_BANDS;
        const HWSEEK_PROG_LIM = bindings::V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
        const ONE_HZ = bindings::V4L2_TUNER_CAP_1HZ;
    }

    #[derive(Clone, Copy, Debug)]
    pub struct TunerTransmissionFlags: u32 {
        const MONO = bindings::V4L2_TUNER_SUB_MONO;
        const STEREO = bindings::V4L2_TUNER_SUB_STEREO;
        const LANG1 = bindings::V4L2_TUNER_SUB_LANG1;
        const LANG2 = bindings::V4L2_TUNER_SUB_LANG2;
        const RDS = bindings::V4L2_TUNER_SUB_RDS;
    }
}

#[derive(Debug, N)]
#[repr(u32)]
pub enum TunerMode {
    Mono = bindings::V4L2_TUNER_MODE_MONO,
    Stereo = bindings::V4L2_TUNER_MODE_STEREO,
    Lang1 = bindings::V4L2_TUNER_MODE_LANG1,
    Lang2 = bindings::V4L2_TUNER_MODE_LANG2,
    Lang1Lang2 = bindings::V4L2_TUNER_MODE_LANG1_LANG2,
}

#[doc(hidden)]
mod ioctl {
    use crate::bindings::v4l2_audio;
    use crate::bindings::v4l2_audioout;
    use crate::bindings::v4l2_frequency;
    use crate::bindings::v4l2_frequency_band;
    use crate::bindings::v4l2_modulator;
    use crate::bindings::v4l2_tuner;

    nix::ioctl_readwrite!(vidioc_g_tuner, b'V', 29, v4l2_tuner);
    nix::ioctl_write_ptr!(vidioc_s_tuner, b'V', 30, v4l2_tuner);

    nix::ioctl_read!(vidioc_g_audio, b'V', 33, v4l2_audio);
    nix::ioctl_write_ptr!(vidioc_s_audio, b'V', 34, v4l2_audio);

    nix::ioctl_read!(vidioc_g_audout, b'V', 49, v4l2_audioout);
    nix::ioctl_write_ptr!(vidioc_s_audout, b'V', 50, v4l2_audioout);

    nix::ioctl_readwrite!(vidioc_g_modulator, b'V', 54, v4l2_modulator);
    nix::ioctl_write_ptr!(vidioc_s_modulator, b'V', 55, v4l2_modulator);

    nix::ioctl_readwrite!(vidioc_g_frequency, b'V', 56, v4l2_frequency);
    nix::ioctl_write_ptr!(vidioc_s_frequency, b'V', 57, v4l2_frequency);

    nix::ioctl_readwrite!(vidioc_enumaudio, b'V', 65, v4l2_audio);
    nix::ioctl_readwrite!(vidioc_enumaudout, b'V', 66, v4l2_audioout);

    nix::ioctl_readwrite!(vidioc_enum_freq_bands, b'V', 101, v4l2_frequency_band);
}

#[derive(Debug, Error)]
pub enum GAudioError {
    #[error("invalid input index")]
    Invalid,
    #[error("ioctl error: {0}")]
    IoctlError(Errno),
}

impl From<GAudioError> for Errno {
    fn from(err: GAudioError) -> Self {
        match err {
            GAudioError::Invalid => Errno::EINVAL,
            GAudioError::IoctlError(e) => e,
        }
    }
}

/// Safe wrapper around the `VIDIOC_G_TUNER` ioctl.
pub fn g_tuner<O: From<v4l2_tuner>>(fd: &impl AsRawFd, index: u32) -> Result<O, GAudioError> {
    let mut tuner = v4l2_tuner {
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_tuner(fd.as_raw_fd(), &mut tuner) } {
        Ok(_) => Ok(O::from(tuner)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_S_TUNER` ioctl.
pub fn s_tuner(fd: &impl AsRawFd, index: u32, mode: TunerMode) -> Result<(), GAudioError> {
    let tuner = v4l2_tuner {
        index,
        audmode: mode as u32,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_tuner(fd.as_raw_fd(), &tuner) } {
        Ok(_) => Ok(()),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_G_AUDIO` ioctl.
pub fn g_audio<O: From<v4l2_audio>>(fd: &impl AsRawFd) -> Result<O, GAudioError> {
    let mut audio = v4l2_audio {
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_audio(fd.as_raw_fd(), &mut audio) } {
        Ok(_) => Ok(O::from(audio)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_S_AUDIO` ioctl.
pub fn s_audio(fd: &impl AsRawFd, index: u32, mode: Option<AudioMode>) -> Result<(), GAudioError> {
    let audio = v4l2_audio {
        index,
        mode: mode.map(|m| m as u32).unwrap_or(0),
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_audio(fd.as_raw_fd(), &audio) } {
        Ok(_) => Ok(()),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_G_AUDOUT` ioctl.
pub fn g_audout<O: From<v4l2_audioout>>(fd: &impl AsRawFd) -> Result<O, GAudioError> {
    let mut audio = v4l2_audioout {
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_audout(fd.as_raw_fd(), &mut audio) } {
        Ok(_) => Ok(O::from(audio)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_S_AUDIO` ioctl.
pub fn s_audout(fd: &impl AsRawFd, index: u32) -> Result<(), GAudioError> {
    let audio = v4l2_audioout {
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_audout(fd.as_raw_fd(), &audio) } {
        Ok(_) => Ok(()),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_G_MODULATOR` ioctl.
pub fn g_modulator<O: From<v4l2_modulator>>(
    fd: &impl AsRawFd,
    index: u32,
) -> Result<O, GAudioError> {
    let mut modulator = v4l2_modulator {
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_modulator(fd.as_raw_fd(), &mut modulator) } {
        Ok(_) => Ok(O::from(modulator)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_S_MODULATOR` ioctl.
pub fn s_modulator(
    fd: &impl AsRawFd,
    index: u32,
    txsubchans: TunerTransmissionFlags,
) -> Result<(), GAudioError> {
    let modulator = v4l2_modulator {
        index,
        txsubchans: txsubchans.bits(),
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_modulator(fd.as_raw_fd(), &modulator) } {
        Ok(_) => Ok(()),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_G_FREQUENCY` ioctl.
pub fn g_frequency<O: From<v4l2_frequency>>(
    fd: &impl AsRawFd,
    tuner: u32,
) -> Result<O, GAudioError> {
    let mut frequency = v4l2_frequency {
        tuner,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_g_frequency(fd.as_raw_fd(), &mut frequency) } {
        Ok(_) => Ok(O::from(frequency)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_S_FREQUENCY` ioctl.
pub fn s_frequency(
    fd: &impl AsRawFd,
    tuner: u32,
    type_: TunerType,
    frequency: u32,
) -> Result<(), GAudioError> {
    let frequency = v4l2_frequency {
        tuner,
        type_: type_ as u32,
        frequency,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_s_frequency(fd.as_raw_fd(), &frequency) } {
        Ok(_) => Ok(()),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_ENUMAUDIO` ioctl.
pub fn enumaudio<O: From<v4l2_audio>>(fd: &impl AsRawFd, index: u32) -> Result<O, GAudioError> {
    let mut audio = v4l2_audio {
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_enumaudio(fd.as_raw_fd(), &mut audio) } {
        Ok(_) => Ok(O::from(audio)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

/// Safe wrapper around the `VIDIOC_ENUMAUDOUT` ioctl.
pub fn enumaudout<O: From<v4l2_audioout>>(fd: &impl AsRawFd, index: u32) -> Result<O, GAudioError> {
    let mut audio = v4l2_audioout {
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_enumaudout(fd.as_raw_fd(), &mut audio) } {
        Ok(_) => Ok(O::from(audio)),
        Err(Errno::EINVAL) => Err(GAudioError::Invalid),
        Err(e) => Err(GAudioError::IoctlError(e)),
    }
}

#[derive(Debug, Error)]
pub enum EnumFreqBandsError {
    #[error("invalid tuner, index, or type")]
    Invalid,
    #[error("ioctl error: {0}")]
    IoctlError(Errno),
}

impl From<EnumFreqBandsError> for Errno {
    fn from(err: EnumFreqBandsError) -> Self {
        match err {
            EnumFreqBandsError::Invalid => Errno::EINVAL,
            EnumFreqBandsError::IoctlError(e) => e,
        }
    }
}
/// Safe wrapper around the `VIDIOC_ENUM_FREQ_BANDS` ioctl.
pub fn enum_freq_bands<O: From<v4l2_frequency_band>>(
    fd: &impl AsRawFd,
    tuner: u32,
    type_: TunerType,
    index: u32,
) -> Result<O, EnumFreqBandsError> {
    let mut freq_band = v4l2_frequency_band {
        tuner,
        type_: type_ as u32,
        index,
        ..Default::default()
    };

    match unsafe { ioctl::vidioc_enum_freq_bands(fd.as_raw_fd(), &mut freq_band) } {
        Ok(_) => Ok(O::from(freq_band)),
        Err(Errno::EINVAL) => Err(EnumFreqBandsError::Invalid),
        Err(e) => Err(EnumFreqBandsError::IoctlError(e)),
    }
}