Skip to main content

pito_codecs/media/
types.rs

1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub enum Codec {
5    H264,
6    Hevc,
7    Aac,
8    Vp9,
9    Opus,
10    Ac3,
11    Eac3,
12    Mp3,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum Route {
17    Gpu,
18    Cpu,
19    Ffmpeg,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum Direction {
24    Decode,
25    Encode,
26}
27
28impl fmt::Display for Codec {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        f.write_str(match self {
31            Codec::H264 => "H.264",
32            Codec::Hevc => "HEVC",
33            Codec::Aac => "AAC",
34            Codec::Vp9 => "VP9",
35            Codec::Opus => "Opus",
36            Codec::Ac3 => "AC-3",
37            Codec::Eac3 => "E-AC-3",
38            Codec::Mp3 => "MP3",
39        })
40    }
41}
42
43impl fmt::Display for Route {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(match self {
46            Route::Gpu => "GPU",
47            Route::Cpu => "CPU",
48            Route::Ffmpeg => "ffmpeg",
49        })
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum PixelFormat {
55    Yuv420p8,
56    Yuv420p10,
57}
58
59impl PixelFormat {
60    pub fn bytes_per_sample(self) -> usize {
61        match self {
62            PixelFormat::Yuv420p8 => 1,
63            PixelFormat::Yuv420p10 => 2,
64        }
65    }
66
67    pub fn bit_depth(self) -> u8 {
68        match self {
69            PixelFormat::Yuv420p8 => 8,
70            PixelFormat::Yuv420p10 => 10,
71        }
72    }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct TimeBase {
77    pub num: u32,
78    pub den: u32,
79}
80
81impl TimeBase {
82    pub const fn new(num: u32, den: u32) -> Self {
83        Self { num, den }
84    }
85
86    pub fn seconds(self, ticks: i64) -> f64 {
87        ticks as f64 * self.num as f64 / self.den as f64
88    }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct Plane {
93    pub data: Vec<u8>,
94    pub stride: usize,
95    pub width: usize,
96    pub height: usize,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Frame {
101    pub format: PixelFormat,
102    pub width: u32,
103    pub height: u32,
104    pub pts: i64,
105    pub time_base: TimeBase,
106    pub planes: [Plane; 3],
107}
108
109impl Frame {
110    pub fn new(
111        format: PixelFormat,
112        width: u32,
113        height: u32,
114        pts: i64,
115        time_base: TimeBase,
116    ) -> Self {
117        let bytes = format.bytes_per_sample();
118        let cw = width.div_ceil(2) as usize;
119        let ch = height.div_ceil(2) as usize;
120        let plane = |w: usize, h: usize| Plane {
121            data: vec![0; w * bytes * h],
122            stride: w * bytes,
123            width: w,
124            height: h,
125        };
126        Self {
127            format,
128            width,
129            height,
130            pts,
131            time_base,
132            planes: [
133                plane(width as usize, height as usize),
134                plane(cw, ch),
135                plane(cw, ch),
136            ],
137        }
138    }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct Packet {
143    pub data: Vec<u8>,
144    pub pts: i64,
145    pub dts: i64,
146    pub keyframe: bool,
147}
148
149#[derive(Debug, Clone, PartialEq)]
150pub struct AudioBuffer {
151    pub samples: Vec<f32>,
152    pub rate: u32,
153    pub channels: u16,
154}
155
156impl AudioBuffer {
157    pub fn new(rate: u32, channels: u16) -> Self {
158        Self {
159            samples: Vec::new(),
160            rate,
161            channels,
162        }
163    }
164
165    pub fn frames(&self) -> usize {
166        if self.channels == 0 {
167            0
168        } else {
169            self.samples.len() / self.channels as usize
170        }
171    }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct Rgba {
176    pub width: u32,
177    pub height: u32,
178    pub data: Vec<u8>,
179}
180
181impl Rgba {
182    pub fn new(width: u32, height: u32) -> Self {
183        Self {
184            width,
185            height,
186            data: vec![0; width as usize * height as usize * 4],
187        }
188    }
189
190    pub fn stride(&self) -> usize {
191        self.width as usize * 4
192    }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub enum Rate {
197    Crf(u8),
198    Bitrate(u32),
199    Vbr { kbps: u32, max_kbps: u32 },
200    Qp(u8),
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204pub enum Bitstream {
205    LengthPrefixed,
206    AnnexB,
207}
208
209#[derive(Debug)]
210pub enum Error {
211    Unsupported(String),
212    Invalid(String),
213    Backend(String),
214    Io(std::io::Error),
215    Cancelled,
216}
217
218impl fmt::Display for Error {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        match self {
221            Error::Unsupported(s) => write!(f, "unsupported: {s}"),
222            Error::Invalid(s) => write!(f, "invalid data: {s}"),
223            Error::Backend(s) => write!(f, "backend: {s}"),
224            Error::Io(e) => write!(f, "{e}"),
225            Error::Cancelled => write!(f, "cancelled"),
226        }
227    }
228}
229
230impl Error {
231    pub fn at(path: &std::path::Path, e: std::io::Error) -> Self {
232        let text = e.to_string();
233        let cause = match text.rfind(" (os error ") {
234            Some(i) if text.ends_with(')') => &text[..i],
235            _ => text.as_str(),
236        };
237        Error::Io(std::io::Error::new(
238            e.kind(),
239            format!("{}: {cause}", path.display()),
240        ))
241    }
242
243    pub fn in_file(self, path: &std::path::Path) -> Self {
244        let shown = path.display().to_string();
245        let named = |s: String| {
246            if s.contains(&shown) {
247                s
248            } else {
249                format!("{shown}: {s}")
250            }
251        };
252        match self {
253            Error::Unsupported(s) => Error::Unsupported(named(s)),
254            Error::Invalid(s) => Error::Invalid(named(s)),
255            Error::Backend(s) => Error::Backend(named(s)),
256            Error::Io(e) if !e.to_string().contains(&shown) => Error::at(path, e),
257            e => e,
258        }
259    }
260}
261
262impl std::error::Error for Error {
263    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
264        match self {
265            Error::Io(e) => Some(e),
266            _ => None,
267        }
268    }
269}
270
271impl From<std::io::Error> for Error {
272    fn from(e: std::io::Error) -> Self {
273        Error::Io(e)
274    }
275}
276
277pub type Result<T> = std::result::Result<T, Error>;
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn odd_sizes_round_chroma_up_and_ten_bit_doubles_stride() {
285        let f = Frame::new(
286            PixelFormat::Yuv420p10,
287            1281,
288            721,
289            0,
290            TimeBase::new(1, 90000),
291        );
292        assert_eq!(f.planes[0].stride, 1281 * 2);
293        assert_eq!((f.planes[1].width, f.planes[1].height), (641, 361));
294        assert_eq!(f.planes[2].data.len(), 641 * 2 * 361);
295    }
296}