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}