1use crate::error::{CodecError, CodecResult};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Ffv1Version {
14 V0,
16 V1,
18 V2,
20 V3,
22}
23
24impl Ffv1Version {
25 pub fn from_u8(v: u8) -> CodecResult<Self> {
27 match v {
28 0 => Ok(Self::V0),
29 1 => Ok(Self::V1),
30 2 => Ok(Self::V2),
31 3 => Ok(Self::V3),
32 _ => Err(CodecError::InvalidParameter(format!(
33 "unsupported FFV1 version: {v}"
34 ))),
35 }
36 }
37
38 #[must_use]
40 pub const fn as_u8(self) -> u8 {
41 match self {
42 Self::V0 => 0,
43 Self::V1 => 1,
44 Self::V2 => 2,
45 Self::V3 => 3,
46 }
47 }
48
49 #[must_use]
51 pub const fn uses_range_coder(self) -> bool {
52 matches!(self, Self::V3 | Self::V2)
53 }
54
55 #[must_use]
57 pub const fn supports_ec(self) -> bool {
58 matches!(self, Self::V3)
59 }
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum Ffv1Colorspace {
65 YCbCr,
67 Rgb,
69}
70
71impl Ffv1Colorspace {
72 pub fn from_u8(v: u8) -> CodecResult<Self> {
74 match v {
75 0 => Ok(Self::YCbCr),
76 1 => Ok(Self::Rgb),
77 _ => Err(CodecError::InvalidParameter(format!(
78 "unsupported FFV1 colorspace: {v}"
79 ))),
80 }
81 }
82
83 #[must_use]
85 pub const fn as_u8(self) -> u8 {
86 match self {
87 Self::YCbCr => 0,
88 Self::Rgb => 1,
89 }
90 }
91
92 #[must_use]
94 pub const fn plane_count(self) -> usize {
95 match self {
96 Self::YCbCr => 3,
97 Self::Rgb => 3,
99 }
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum Ffv1ChromaType {
106 Chroma420,
108 Chroma422,
110 Chroma444,
112}
113
114impl Ffv1ChromaType {
115 #[must_use]
117 pub const fn h_shift(self) -> u32 {
118 match self {
119 Self::Chroma420 | Self::Chroma422 => 1,
120 Self::Chroma444 => 0,
121 }
122 }
123
124 #[must_use]
126 pub const fn v_shift(self) -> u32 {
127 match self {
128 Self::Chroma420 => 1,
129 Self::Chroma422 | Self::Chroma444 => 0,
130 }
131 }
132
133 pub fn from_shifts(h_shift: u32, v_shift: u32) -> CodecResult<Self> {
135 match (h_shift, v_shift) {
136 (1, 1) => Ok(Self::Chroma420),
137 (1, 0) => Ok(Self::Chroma422),
138 (0, 0) => Ok(Self::Chroma444),
139 _ => Err(CodecError::InvalidParameter(format!(
140 "unsupported chroma subsampling: h_shift={h_shift}, v_shift={v_shift}"
141 ))),
142 }
143 }
144}
145
146#[derive(Clone, Debug)]
151pub struct Ffv1Config {
152 pub version: Ffv1Version,
154 pub width: u32,
156 pub height: u32,
158 pub colorspace: Ffv1Colorspace,
160 pub chroma_type: Ffv1ChromaType,
162 pub bits_per_raw_sample: u8,
164 pub num_h_slices: u32,
166 pub num_v_slices: u32,
168 pub ec: bool,
170 pub state_transition_delta: Vec<i16>,
172 pub range_coder_mode: bool,
175}
176
177impl Default for Ffv1Config {
178 fn default() -> Self {
179 Self {
180 version: Ffv1Version::V3,
181 width: 0,
182 height: 0,
183 colorspace: Ffv1Colorspace::YCbCr,
184 chroma_type: Ffv1ChromaType::Chroma420,
185 bits_per_raw_sample: 8,
186 num_h_slices: 1,
187 num_v_slices: 1,
188 ec: true,
189 state_transition_delta: Vec::new(),
190 range_coder_mode: true,
191 }
192 }
193}
194
195impl Ffv1Config {
196 #[must_use]
198 pub fn num_slices(&self) -> u32 {
199 self.num_h_slices * self.num_v_slices
200 }
201
202 #[must_use]
204 pub fn max_sample_value(&self) -> i32 {
205 (1i32 << self.bits_per_raw_sample) - 1
206 }
207
208 #[must_use]
211 pub fn bytes_per_sample(&self) -> usize {
212 if self.bits_per_raw_sample <= 8 {
213 1
214 } else {
215 2
216 }
217 }
218
219 #[must_use]
221 pub fn plane_count(&self) -> usize {
222 self.colorspace.plane_count()
223 }
224
225 #[must_use]
227 pub fn plane_dimensions(&self, plane_index: usize) -> (u32, u32) {
228 if plane_index == 0 || self.colorspace == Ffv1Colorspace::Rgb {
229 (self.width, self.height)
230 } else {
231 let w =
232 (self.width + (1 << self.chroma_type.h_shift()) - 1) >> self.chroma_type.h_shift();
233 let h =
234 (self.height + (1 << self.chroma_type.v_shift()) - 1) >> self.chroma_type.v_shift();
235 (w, h)
236 }
237 }
238
239 pub fn validate(&self) -> CodecResult<()> {
241 if self.width == 0 || self.height == 0 {
242 return Err(CodecError::InvalidParameter(
243 "frame dimensions must be nonzero".to_string(),
244 ));
245 }
246 if !matches!(self.bits_per_raw_sample, 8 | 10 | 12 | 16) {
247 return Err(CodecError::InvalidParameter(format!(
248 "unsupported bits_per_raw_sample: {}",
249 self.bits_per_raw_sample
250 )));
251 }
252 if self.num_h_slices == 0 || self.num_v_slices == 0 {
253 return Err(CodecError::InvalidParameter(
254 "slice counts must be nonzero".to_string(),
255 ));
256 }
257 Ok(())
258 }
259}
260
261#[derive(Clone, Debug, Default)]
263pub struct SliceHeader {
264 pub slice_x: u32,
266 pub slice_y: u32,
268 pub slice_width: u32,
270 pub slice_height: u32,
272}
273
274pub const CONTEXT_COUNT: usize = 32;
277
278pub const INITIAL_STATE: u8 = 128;
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 #[ignore]
287 fn test_version_roundtrip() {
288 for v in [
289 Ffv1Version::V0,
290 Ffv1Version::V1,
291 Ffv1Version::V2,
292 Ffv1Version::V3,
293 ] {
294 let n = v.as_u8();
295 let parsed = Ffv1Version::from_u8(n).expect("valid version");
296 assert_eq!(parsed, v);
297 }
298 }
299
300 #[test]
301 #[ignore]
302 fn test_version_invalid() {
303 assert!(Ffv1Version::from_u8(4).is_err());
304 }
305
306 #[test]
307 #[ignore]
308 fn test_colorspace() {
309 assert_eq!(Ffv1Colorspace::YCbCr.plane_count(), 3);
310 assert_eq!(Ffv1Colorspace::Rgb.plane_count(), 3);
311 assert_eq!(Ffv1Colorspace::YCbCr.as_u8(), 0);
312 assert_eq!(Ffv1Colorspace::Rgb.as_u8(), 1);
313 }
314
315 #[test]
316 #[ignore]
317 fn test_chroma_type_shifts() {
318 assert_eq!(Ffv1ChromaType::Chroma420.h_shift(), 1);
319 assert_eq!(Ffv1ChromaType::Chroma420.v_shift(), 1);
320 assert_eq!(Ffv1ChromaType::Chroma422.h_shift(), 1);
321 assert_eq!(Ffv1ChromaType::Chroma422.v_shift(), 0);
322 assert_eq!(Ffv1ChromaType::Chroma444.h_shift(), 0);
323 assert_eq!(Ffv1ChromaType::Chroma444.v_shift(), 0);
324 }
325
326 #[test]
327 #[ignore]
328 fn test_chroma_type_from_shifts() {
329 assert_eq!(
330 Ffv1ChromaType::from_shifts(1, 1).expect("valid"),
331 Ffv1ChromaType::Chroma420
332 );
333 assert_eq!(
334 Ffv1ChromaType::from_shifts(1, 0).expect("valid"),
335 Ffv1ChromaType::Chroma422
336 );
337 assert_eq!(
338 Ffv1ChromaType::from_shifts(0, 0).expect("valid"),
339 Ffv1ChromaType::Chroma444
340 );
341 assert!(Ffv1ChromaType::from_shifts(2, 0).is_err());
342 }
343
344 #[test]
345 #[ignore]
346 fn test_config_plane_dimensions() {
347 let config = Ffv1Config {
348 width: 1920,
349 height: 1080,
350 chroma_type: Ffv1ChromaType::Chroma420,
351 ..Default::default()
352 };
353 assert_eq!(config.plane_dimensions(0), (1920, 1080));
354 assert_eq!(config.plane_dimensions(1), (960, 540));
355 assert_eq!(config.plane_dimensions(2), (960, 540));
356 }
357
358 #[test]
359 #[ignore]
360 fn test_config_validation() {
361 let mut config = Ffv1Config {
362 width: 1920,
363 height: 1080,
364 ..Default::default()
365 };
366 assert!(config.validate().is_ok());
367
368 config.width = 0;
369 assert!(config.validate().is_err());
370
371 config.width = 1920;
372 config.bits_per_raw_sample = 9;
373 assert!(config.validate().is_err());
374 }
375
376 #[test]
377 #[ignore]
378 fn test_max_sample_value() {
379 let config = Ffv1Config {
380 bits_per_raw_sample: 8,
381 ..Default::default()
382 };
383 assert_eq!(config.max_sample_value(), 255);
384
385 let config10 = Ffv1Config {
386 bits_per_raw_sample: 10,
387 ..Default::default()
388 };
389 assert_eq!(config10.max_sample_value(), 1023);
390 }
391}