mp4_stream/
config.rs

1//! Types for stream and camera configuration.
2
3#[cfg(feature = "serde")]
4use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
5#[cfg(feature = "serde")]
6use serde_repr::{Deserialize_repr, Serialize_repr};
7use std::{collections::HashMap, fmt, path::PathBuf};
8
9/// The main configuration struct.
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Config {
13    /// The v4l2 device to capture video with (eg. "/dev/video0").
14    pub device: PathBuf,
15    /// The fourCC code to capture in (eg. "YUYV").
16    pub format: Format,
17    /// Pixel resolution (width, height).
18    pub resolution: (u32, u32),
19    /// A fraction representing the framerate.
20    pub interval: (u32, u32),
21    /// The rotation for the MP4 matrix. This is not supported by some media players.
22    pub rotation: Rotation,
23    /// Additional controls to pass to V4L2.
24    #[cfg_attr(feature = "serde", serde(rename = "v4l2Controls"))]
25    pub v4l2_controls: HashMap<String, String>,
26}
27
28impl Default for Config {
29    fn default() -> Self {
30        Self {
31            device: PathBuf::from("/dev/video0"),
32            format: Format::YUYV,
33            resolution: (640, 480),
34            interval: (1, 30),
35            rotation: Rotation::R0,
36            v4l2_controls: HashMap::new(),
37        }
38    }
39}
40
41/// The fourCC code to capture in.
42///
43/// Currently supported formats are H264 and those supported by libx264.
44#[allow(clippy::upper_case_acronyms)]
45#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
46#[repr(u32)]
47pub enum Format {
48    /// H264 format. Selecting this option will disable software encoding, which is
49    /// generally much faster but can reduce quality or compression ratio.
50    H264 = u32::from_be_bytes(*b"H264"),
51    /// YUYV format. In this format, 4 bytes encode 2 pixels, with the first encoding the
52    /// Y or luminance component for the first pixel, the second encoding the U or Cb component for
53    /// both pixels, the third encoding the Y component for the second pixel, and the fourth
54    /// encoding the V or Cr component for both pixels.
55    YUYV = u32::from_be_bytes(*b"YUYV"),
56    /// YV12 format. 6 bytes encode 4 pixels, with the first 4 bytes encoding the Y
57    /// component for each pixel, the fith byte encoding the V component for all 4 pixels,
58    /// and the last byte encoding the U component for all 4 pixels. The "12" refers to the
59    /// format's bit depth.
60    YV12 = u32::from_be_bytes(*b"YV12"),
61    /// RGB format. 3 bytes encode 1 pixel, with the first encoding the red component,
62    /// the second encoding the green component, and the third encoding the blue component.
63    RGB3 = u32::from_be_bytes(*b"RGB3"),
64    /// BGR format. 3 bytes encode 1 pixel, with the first encoding the blue component,
65    /// the second encoding the green component, and the third encoding the red component.
66    BGR3 = u32::from_be_bytes(*b"BGR3"),
67}
68
69impl From<Format> for [u8; 4] {
70    fn from(format: Format) -> Self {
71        (format as u32).to_be_bytes()
72    }
73}
74
75impl TryFrom<u32> for Format {
76    type Error = String;
77
78    fn try_from(value: u32) -> Result<Self, Self::Error> {
79        match &value.to_be_bytes() {
80            b"H264" => Ok(Self::H264),
81            b"YUYV" => Ok(Self::YUYV),
82            b"YV12" => Ok(Self::YV12),
83            b"RGB3" => Ok(Self::RGB3),
84            b"BGR3" => Ok(Self::BGR3),
85            other => Err(format!("Invalid fourCC: {:?}", std::str::from_utf8(other))),
86        }
87    }
88}
89
90impl TryFrom<[u8; 4]> for Format {
91    type Error = String;
92
93    fn try_from(value: [u8; 4]) -> Result<Self, Self::Error> {
94        u32::from_be_bytes(value).try_into()
95    }
96}
97
98impl fmt::Display for Format {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        let bytes = &(*self as u32).to_be_bytes();
101        #[allow(clippy::unwrap_used)] // all enum variants are valid UTF-8
102        let format = std::str::from_utf8(bytes).unwrap();
103        write!(f, "{format}")
104    }
105}
106
107#[cfg(feature = "serde")]
108impl Serialize for Format {
109    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
110        self.to_string().serialize(s)
111    }
112}
113
114#[cfg(feature = "serde")]
115impl<'de> Deserialize<'de> for Format {
116    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
117        let format = String::deserialize(d)?;
118        let format = format.as_bytes();
119        let format = [format[0], format[1], format[2], format[3]];
120        Self::try_from(u32::from_be_bytes(format)).map_err(D::Error::custom)
121    }
122}
123
124/// The rotation for the MP4 rotation matrix.
125///
126/// This is not supported by some media players.
127#[cfg_attr(feature = "serde", derive(Serialize_repr, Deserialize_repr))]
128#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
129#[repr(u32)]
130pub enum Rotation {
131    /// 0 degrees of rotation.
132    R0 = 0,
133    /// 90 degrees of rotation.
134    R90 = 90,
135    /// 180 degrees of rotation.
136    R180 = 180,
137    /// 270 degrees of rotation.
138    R270 = 270,
139}