1use crate::error::{CodecError, CodecResult};
8use crate::frame::VideoFrame;
9use crate::traits::{EncodedPacket, EncoderConfig, VideoEncoder};
10use oximedia_core::CodecId;
11
12use super::crc32::crc32_mpeg2;
13use super::prediction::predict_median;
14use super::range_coder::SimpleRangeEncoder;
15use super::types::{
16 Ffv1ChromaType, Ffv1Colorspace, Ffv1Config, Ffv1Version, CONTEXT_COUNT, INITIAL_STATE,
17};
18
19pub struct Ffv1Encoder {
38 config: EncoderConfig,
40 ffv1_config: Ffv1Config,
42 output_queue: Vec<EncodedPacket>,
44 flushing: bool,
46 frame_count: u64,
48 plane_states: Vec<Vec<u8>>,
50}
51
52impl Ffv1Encoder {
53 pub fn new(config: EncoderConfig) -> CodecResult<Self> {
55 let ffv1_config = Ffv1Config {
56 version: Ffv1Version::V3,
57 width: config.width,
58 height: config.height,
59 colorspace: Ffv1Colorspace::YCbCr,
60 chroma_type: Ffv1ChromaType::Chroma420,
61 bits_per_raw_sample: 8,
62 num_h_slices: 1,
63 num_v_slices: 1,
64 ec: true,
65 range_coder_mode: true,
66 state_transition_delta: Vec::new(),
67 };
68 Self::with_ffv1_config(config, ffv1_config)
69 }
70
71 pub fn with_ffv1_config(config: EncoderConfig, ffv1: Ffv1Config) -> CodecResult<Self> {
73 if config.width == 0 || config.height == 0 {
74 return Err(CodecError::InvalidParameter(
75 "frame dimensions must be nonzero".to_string(),
76 ));
77 }
78
79 let mut ffv1_config = ffv1;
80 ffv1_config.width = config.width;
81 ffv1_config.height = config.height;
82 ffv1_config.validate()?;
83
84 let plane_count = ffv1_config.plane_count();
85 let plane_states: Vec<Vec<u8>> = (0..plane_count)
86 .map(|_| vec![INITIAL_STATE; CONTEXT_COUNT])
87 .collect();
88
89 Ok(Self {
90 config,
91 ffv1_config,
92 output_queue: Vec::new(),
93 flushing: false,
94 frame_count: 0,
95 plane_states,
96 })
97 }
98
99 fn reset_states(&mut self) {
101 for states in &mut self.plane_states {
102 for s in states.iter_mut() {
103 *s = INITIAL_STATE;
104 }
105 }
106 }
107
108 #[must_use]
113 pub fn extradata(&self) -> Vec<u8> {
114 let c = &self.ffv1_config;
115 let mut data = Vec::with_capacity(16);
116 data.push(c.version.as_u8());
117 data.push(c.colorspace.as_u8());
118 data.push(c.chroma_type.h_shift() as u8);
119 data.push(c.chroma_type.v_shift() as u8);
120 data.push(c.bits_per_raw_sample);
121 data.push(if c.ec { 1 } else { 0 });
122 data.push(c.num_h_slices as u8);
123 data.push(c.num_v_slices as u8);
124 data.extend_from_slice(&c.width.to_le_bytes());
125 data.extend_from_slice(&c.height.to_le_bytes());
126 data
127 }
128
129 fn encode_frame(&mut self, frame: &VideoFrame) -> CodecResult<Vec<u8>> {
131 let plane_count = self.ffv1_config.plane_count();
133 let cfg_width = self.ffv1_config.width;
134 let cfg_height = self.ffv1_config.height;
135 let ec = self.ffv1_config.ec;
136 let version = self.ffv1_config.version;
137 let is_keyframe = self.frame_count % u64::from(self.config.keyint) == 0;
138
139 let plane_dims: Vec<(u32, u32)> = (0..plane_count)
141 .map(|i| self.ffv1_config.plane_dimensions(i))
142 .collect();
143
144 if is_keyframe {
145 self.reset_states();
146 }
147
148 if frame.width != cfg_width || frame.height != cfg_height {
150 return Err(CodecError::InvalidParameter(format!(
151 "frame dimensions {}x{} do not match encoder config {}x{}",
152 frame.width, frame.height, cfg_width, cfg_height
153 )));
154 }
155
156 if frame.planes.len() < plane_count {
157 return Err(CodecError::InvalidParameter(format!(
158 "frame has {} planes, need at least {}",
159 frame.planes.len(),
160 plane_count
161 )));
162 }
163
164 let mut encoder = SimpleRangeEncoder::new();
166
167 for plane_idx in 0..plane_count {
168 let (pw, ph) = plane_dims[plane_idx];
169 let plane = &frame.planes[plane_idx];
170
171 let states = &mut self.plane_states[plane_idx];
172 let mut prev_line = vec![0i32; pw as usize];
173
174 for y in 0..ph as usize {
175 for x in 0..pw as usize {
176 let sample = if y < plane.height as usize && x < plane.width as usize {
178 i32::from(plane.data[y * plane.stride + x])
179 } else {
180 0
181 };
182
183 let left = if x > 0 {
185 i32::from(plane.data[y * plane.stride + x - 1])
188 } else {
189 0
190 };
191 let top = prev_line[x];
192 let top_left = if x > 0 { prev_line[x - 1] } else { 0 };
193
194 let pred = predict_median(left, top, top_left);
195 let residual = sample - pred;
196
197 encoder.put_symbol(states, residual);
198
199 if x == 0 && y > 0 {
201 }
203 }
204
205 for x in 0..pw as usize {
207 prev_line[x] = if y < plane.height as usize && x < plane.width as usize {
208 i32::from(plane.data[y * plane.stride + x])
209 } else {
210 0
211 };
212 }
213 }
214 }
215
216 let mut payload = encoder.finish();
217
218 if ec && version == Ffv1Version::V3 {
220 let crc = crc32_mpeg2(&payload);
221 payload.extend_from_slice(&crc.to_le_bytes());
222 }
223
224 Ok(payload)
225 }
226}
227
228impl VideoEncoder for Ffv1Encoder {
229 fn codec(&self) -> CodecId {
230 CodecId::Ffv1
231 }
232
233 fn send_frame(&mut self, frame: &VideoFrame) -> CodecResult<()> {
234 if self.flushing {
235 return Err(CodecError::InvalidParameter(
236 "encoder is flushing, cannot accept new frames".to_string(),
237 ));
238 }
239
240 let pts = frame.timestamp.pts;
241 let is_keyframe = self.frame_count % u64::from(self.config.keyint) == 0;
242
243 let data = self.encode_frame(frame)?;
244
245 let packet = EncodedPacket {
246 data,
247 pts,
248 dts: pts,
249 keyframe: is_keyframe,
250 duration: None,
251 };
252
253 self.output_queue.push(packet);
254 self.frame_count += 1;
255 Ok(())
256 }
257
258 fn receive_packet(&mut self) -> CodecResult<Option<EncodedPacket>> {
259 if self.output_queue.is_empty() {
260 Ok(None)
261 } else {
262 Ok(Some(self.output_queue.remove(0)))
263 }
264 }
265
266 fn flush(&mut self) -> CodecResult<()> {
267 self.flushing = true;
268 Ok(())
269 }
270
271 fn config(&self) -> &EncoderConfig {
272 &self.config
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::frame::Plane;
280 use crate::traits::VideoDecoder;
281 use oximedia_core::{PixelFormat, Rational, Timestamp};
282
283 fn make_encoder_config(width: u32, height: u32) -> EncoderConfig {
284 EncoderConfig {
285 codec: CodecId::Ffv1,
286 width,
287 height,
288 pixel_format: PixelFormat::Yuv420p,
289 framerate: Rational::new(30, 1),
290 bitrate: crate::traits::BitrateMode::Lossless,
291 preset: crate::traits::EncoderPreset::Medium,
292 profile: None,
293 keyint: 1,
294 threads: 1,
295 timebase: Rational::new(1, 1000),
296 }
297 }
298
299 fn make_test_frame(width: u32, height: u32) -> VideoFrame {
300 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
301 frame.timestamp = Timestamp::new(0, Rational::new(1, 1000));
302
303 let y_size = (width * height) as usize;
305 let mut y_data = vec![0u8; y_size];
306 for y in 0..height as usize {
307 for x in 0..width as usize {
308 y_data[y * width as usize + x] = ((x + y) % 256) as u8;
309 }
310 }
311 frame.planes.push(Plane::with_dimensions(
312 y_data,
313 width as usize,
314 width,
315 height,
316 ));
317
318 let cw = (width + 1) / 2;
320 let ch = (height + 1) / 2;
321 let u_data = vec![128u8; (cw * ch) as usize];
322 frame
323 .planes
324 .push(Plane::with_dimensions(u_data, cw as usize, cw, ch));
325
326 let v_data = vec![128u8; (cw * ch) as usize];
328 frame
329 .planes
330 .push(Plane::with_dimensions(v_data, cw as usize, cw, ch));
331
332 frame
333 }
334
335 #[test]
336 #[ignore]
337 fn test_encoder_creation() {
338 let config = make_encoder_config(320, 240);
339 let enc = Ffv1Encoder::new(config).expect("valid config");
340 assert_eq!(enc.codec(), CodecId::Ffv1);
341 }
342
343 #[test]
344 #[ignore]
345 fn test_encoder_invalid_dimensions() {
346 let config = make_encoder_config(0, 240);
347 assert!(Ffv1Encoder::new(config).is_err());
348 }
349
350 #[test]
351 #[ignore]
352 fn test_encoder_extradata() {
353 let config = make_encoder_config(320, 240);
354 let enc = Ffv1Encoder::new(config).expect("valid");
355 let extra = enc.extradata();
356 assert!(extra.len() >= 13);
357 assert_eq!(extra[0], 3); }
359
360 #[test]
361 #[ignore]
362 fn test_encode_single_frame() {
363 let config = make_encoder_config(16, 16);
364 let mut enc = Ffv1Encoder::new(config).expect("valid");
365 let frame = make_test_frame(16, 16);
366
367 enc.send_frame(&frame).expect("encode ok");
368 let packet = enc.receive_packet().expect("ok");
369 assert!(packet.is_some());
370 let pkt = packet.expect("packet");
371 assert!(pkt.keyframe);
372 assert!(!pkt.data.is_empty());
373 }
374
375 #[test]
376 #[ignore]
377 fn test_encode_wrong_dimensions() {
378 let config = make_encoder_config(16, 16);
379 let mut enc = Ffv1Encoder::new(config).expect("valid");
380 let frame = make_test_frame(32, 32); assert!(enc.send_frame(&frame).is_err());
382 }
383
384 #[test]
385 #[ignore]
386 fn test_encoder_flush() {
387 let config = make_encoder_config(16, 16);
388 let mut enc = Ffv1Encoder::new(config).expect("valid");
389 enc.flush().expect("flush ok");
390 let frame = make_test_frame(16, 16);
391 assert!(enc.send_frame(&frame).is_err());
392 }
393
394 #[test]
395 #[ignore]
396 fn test_lossless_roundtrip() {
397 let width = 16u32;
399 let height = 16u32;
400
401 let enc_config = make_encoder_config(width, height);
402 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
403 let frame = make_test_frame(width, height);
404
405 encoder.send_frame(&frame).expect("encode");
406 let packet = encoder.receive_packet().expect("ok").expect("has packet");
407
408 let extradata = encoder.extradata();
410 let mut decoder =
411 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec init");
412
413 decoder
414 .send_packet(&packet.data, packet.pts)
415 .expect("decode");
416 let decoded_frame = decoder.receive_frame().expect("ok").expect("has frame");
417
418 assert_eq!(decoded_frame.planes.len(), frame.planes.len());
420 for (pi, (orig_plane, dec_plane)) in frame
421 .planes
422 .iter()
423 .zip(decoded_frame.planes.iter())
424 .enumerate()
425 {
426 assert_eq!(
427 orig_plane.width, dec_plane.width,
428 "plane {pi} width mismatch"
429 );
430 assert_eq!(
431 orig_plane.height, dec_plane.height,
432 "plane {pi} height mismatch"
433 );
434
435 for y in 0..orig_plane.height as usize {
436 for x in 0..orig_plane.width as usize {
437 let orig_sample = orig_plane.data[y * orig_plane.stride + x];
438 let dec_sample = dec_plane.data[y * dec_plane.stride + x];
439 assert_eq!(
440 orig_sample, dec_sample,
441 "plane {pi} sample mismatch at ({x}, {y}): orig={orig_sample}, decoded={dec_sample}"
442 );
443 }
444 }
445 }
446 }
447
448 #[test]
449 #[ignore]
450 fn test_lossless_roundtrip_constant_frame() {
451 let width = 8u32;
452 let height = 8u32;
453 let enc_config = make_encoder_config(width, height);
454 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
455
456 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
458 frame.timestamp = Timestamp::new(0, Rational::new(1, 1000));
459 let y_data = vec![100u8; (width * height) as usize];
460 frame.planes.push(Plane::with_dimensions(
461 y_data,
462 width as usize,
463 width,
464 height,
465 ));
466 let cw = (width + 1) / 2;
467 let ch = (height + 1) / 2;
468 frame.planes.push(Plane::with_dimensions(
469 vec![128u8; (cw * ch) as usize],
470 cw as usize,
471 cw,
472 ch,
473 ));
474 frame.planes.push(Plane::with_dimensions(
475 vec![128u8; (cw * ch) as usize],
476 cw as usize,
477 cw,
478 ch,
479 ));
480
481 encoder.send_frame(&frame).expect("encode");
482 let packet = encoder.receive_packet().expect("ok").expect("packet");
483
484 let extradata = encoder.extradata();
485 let mut decoder =
486 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec");
487
488 decoder.send_packet(&packet.data, 0).expect("decode");
489 let decoded = decoder.receive_frame().expect("ok").expect("frame");
490
491 for (pi, (orig, dec)) in frame.planes.iter().zip(decoded.planes.iter()).enumerate() {
492 for y in 0..orig.height as usize {
493 for x in 0..orig.width as usize {
494 assert_eq!(
495 orig.data[y * orig.stride + x],
496 dec.data[y * dec.stride + x],
497 "mismatch at plane {pi} ({x}, {y})"
498 );
499 }
500 }
501 }
502 }
503
504 #[test]
505 #[ignore]
506 fn test_lossless_roundtrip_random_pattern() {
507 let width = 32u32;
508 let height = 32u32;
509 let enc_config = make_encoder_config(width, height);
510 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
511
512 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
513 frame.timestamp = Timestamp::new(1000, Rational::new(1, 1000));
514
515 let y_size = (width * height) as usize;
517 let mut y_data = vec![0u8; y_size];
518 for i in 0..y_size {
519 y_data[i] = ((i * 37 + 13) % 256) as u8;
521 }
522 frame.planes.push(Plane::with_dimensions(
523 y_data,
524 width as usize,
525 width,
526 height,
527 ));
528 let cw = (width + 1) / 2;
529 let ch = (height + 1) / 2;
530 let uv_size = (cw * ch) as usize;
531 let mut u_data = vec![0u8; uv_size];
532 let mut v_data = vec![0u8; uv_size];
533 for i in 0..uv_size {
534 u_data[i] = ((i * 53 + 7) % 256) as u8;
535 v_data[i] = ((i * 71 + 23) % 256) as u8;
536 }
537 frame
538 .planes
539 .push(Plane::with_dimensions(u_data, cw as usize, cw, ch));
540 frame
541 .planes
542 .push(Plane::with_dimensions(v_data, cw as usize, cw, ch));
543
544 encoder.send_frame(&frame).expect("encode");
545 let packet = encoder.receive_packet().expect("ok").expect("packet");
546
547 let extradata = encoder.extradata();
548 let mut decoder =
549 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec");
550
551 decoder.send_packet(&packet.data, 1000).expect("decode");
552 let decoded = decoder.receive_frame().expect("ok").expect("frame");
553
554 for (pi, (orig, dec)) in frame.planes.iter().zip(decoded.planes.iter()).enumerate() {
555 for y in 0..orig.height as usize {
556 for x in 0..orig.width as usize {
557 assert_eq!(
558 orig.data[y * orig.stride + x],
559 dec.data[y * dec.stride + x],
560 "mismatch at plane {pi} ({x}, {y})"
561 );
562 }
563 }
564 }
565 }
566}