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 pub fn new_with_bit_depth(config: EncoderConfig, bits: u8) -> CodecResult<Self> {
104 let ffv1_config = Ffv1Config {
105 version: Ffv1Version::V3,
106 width: config.width,
107 height: config.height,
108 colorspace: Ffv1Colorspace::YCbCr,
109 chroma_type: Ffv1ChromaType::Chroma420,
110 bits_per_raw_sample: bits,
111 num_h_slices: 1,
112 num_v_slices: 1,
113 ec: true,
114 range_coder_mode: true,
115 state_transition_delta: Vec::new(),
116 };
117 Self::with_ffv1_config(config, ffv1_config)
118 }
119
120 fn reset_states(&mut self) {
122 for states in &mut self.plane_states {
123 for s in states.iter_mut() {
124 *s = INITIAL_STATE;
125 }
126 }
127 }
128
129 #[must_use]
134 pub fn extradata(&self) -> Vec<u8> {
135 let c = &self.ffv1_config;
136 let mut data = Vec::with_capacity(16);
137 data.push(c.version.as_u8());
138 data.push(c.colorspace.as_u8());
139 data.push(c.chroma_type.h_shift() as u8);
140 data.push(c.chroma_type.v_shift() as u8);
141 data.push(c.bits_per_raw_sample);
142 data.push(if c.ec { 1 } else { 0 });
143 data.push(c.num_h_slices as u8);
144 data.push(c.num_v_slices as u8);
145 data.extend_from_slice(&c.width.to_le_bytes());
146 data.extend_from_slice(&c.height.to_le_bytes());
147 data
148 }
149
150 fn read_sample(
156 plane_data: &[u8],
157 stride: usize,
158 y: usize,
159 x: usize,
160 bps: u8,
161 ) -> CodecResult<i32> {
162 if bps <= 8 {
163 let idx = y * stride + x;
164 if idx >= plane_data.len() {
165 return Err(CodecError::InvalidBitstream(
166 "plane data too short for 8-bit sample".to_string(),
167 ));
168 }
169 Ok(i32::from(plane_data[idx]))
170 } else {
171 let base = y * stride + x * 2;
173 if base + 1 >= plane_data.len() {
174 return Err(CodecError::InvalidBitstream(
175 "plane data too short for 16-bit sample".to_string(),
176 ));
177 }
178 let lo = plane_data[base] as i32;
179 let hi = plane_data[base + 1] as i32;
180 Ok(lo | (hi << 8))
181 }
182 }
183
184 fn encode_frame(&mut self, frame: &VideoFrame) -> CodecResult<Vec<u8>> {
186 let plane_count = self.ffv1_config.plane_count();
188 let cfg_width = self.ffv1_config.width;
189 let cfg_height = self.ffv1_config.height;
190 let ec = self.ffv1_config.ec;
191 let version = self.ffv1_config.version;
192 let bps = self.ffv1_config.bits_per_raw_sample;
193 let is_keyframe = self.frame_count % u64::from(self.config.keyint) == 0;
194
195 let plane_dims: Vec<(u32, u32)> = (0..plane_count)
197 .map(|i| self.ffv1_config.plane_dimensions(i))
198 .collect();
199
200 if is_keyframe {
201 self.reset_states();
202 }
203
204 if frame.width != cfg_width || frame.height != cfg_height {
206 return Err(CodecError::InvalidParameter(format!(
207 "frame dimensions {}x{} do not match encoder config {}x{}",
208 frame.width, frame.height, cfg_width, cfg_height
209 )));
210 }
211
212 if frame.planes.len() < plane_count {
213 return Err(CodecError::InvalidParameter(format!(
214 "frame has {} planes, need at least {}",
215 frame.planes.len(),
216 plane_count
217 )));
218 }
219
220 let mut encoder = SimpleRangeEncoder::new();
222
223 for plane_idx in 0..plane_count {
224 let (pw, ph) = plane_dims[plane_idx];
225 let plane = &frame.planes[plane_idx];
226 let stride = plane.stride; let states = &mut self.plane_states[plane_idx];
229 let mut prev_line = vec![0i32; pw as usize];
230
231 for y in 0..ph as usize {
232 for x in 0..pw as usize {
233 let sample = if y < plane.height as usize && x < plane.width as usize {
235 Self::read_sample(&plane.data, stride, y, x, bps)?
236 } else {
237 0
238 };
239
240 let left = if x > 0 && y < plane.height as usize && x - 1 < plane.width as usize
242 {
243 Self::read_sample(&plane.data, stride, y, x - 1, bps)?
244 } else {
245 0
246 };
247
248 let top = prev_line[x];
249 let top_left = if x > 0 { prev_line[x - 1] } else { 0 };
250
251 let pred = predict_median(left, top, top_left);
252 let residual = sample - pred;
253
254 encoder.put_symbol(states, residual);
255 }
256
257 for x in 0..pw as usize {
259 prev_line[x] = if y < plane.height as usize && x < plane.width as usize {
260 Self::read_sample(&plane.data, stride, y, x, bps).unwrap_or_default()
261 } else {
262 0
263 };
264 }
265 }
266 }
267
268 let mut payload = encoder.finish();
269
270 if ec && version == Ffv1Version::V3 {
272 let crc = crc32_mpeg2(&payload);
273 payload.extend_from_slice(&crc.to_le_bytes());
274 }
275
276 Ok(payload)
277 }
278}
279
280impl VideoEncoder for Ffv1Encoder {
281 fn codec(&self) -> CodecId {
282 CodecId::Ffv1
283 }
284
285 fn send_frame(&mut self, frame: &VideoFrame) -> CodecResult<()> {
286 if self.flushing {
287 return Err(CodecError::InvalidParameter(
288 "encoder is flushing, cannot accept new frames".to_string(),
289 ));
290 }
291
292 let pts = frame.timestamp.pts;
293 let is_keyframe = self.frame_count % u64::from(self.config.keyint) == 0;
294
295 let data = self.encode_frame(frame)?;
296
297 let packet = EncodedPacket {
298 data,
299 pts,
300 dts: pts,
301 keyframe: is_keyframe,
302 duration: None,
303 };
304
305 self.output_queue.push(packet);
306 self.frame_count += 1;
307 Ok(())
308 }
309
310 fn receive_packet(&mut self) -> CodecResult<Option<EncodedPacket>> {
311 if self.output_queue.is_empty() {
312 Ok(None)
313 } else {
314 Ok(Some(self.output_queue.remove(0)))
315 }
316 }
317
318 fn flush(&mut self) -> CodecResult<()> {
319 self.flushing = true;
320 Ok(())
321 }
322
323 fn config(&self) -> &EncoderConfig {
324 &self.config
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::frame::Plane;
332 use crate::traits::VideoDecoder;
333 use oximedia_core::{PixelFormat, Rational, Timestamp};
334
335 fn make_encoder_config(width: u32, height: u32) -> EncoderConfig {
336 EncoderConfig {
337 codec: CodecId::Ffv1,
338 width,
339 height,
340 pixel_format: PixelFormat::Yuv420p,
341 framerate: Rational::new(30, 1),
342 bitrate: crate::traits::BitrateMode::Lossless,
343 preset: crate::traits::EncoderPreset::Medium,
344 profile: None,
345 keyint: 1,
346 threads: 1,
347 timebase: Rational::new(1, 1000),
348 }
349 }
350
351 fn make_test_frame(width: u32, height: u32) -> VideoFrame {
352 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
353 frame.timestamp = Timestamp::new(0, Rational::new(1, 1000));
354
355 let y_size = (width * height) as usize;
357 let mut y_data = vec![0u8; y_size];
358 for y in 0..height as usize {
359 for x in 0..width as usize {
360 y_data[y * width as usize + x] = ((x + y) % 256) as u8;
361 }
362 }
363 frame.planes.push(Plane::with_dimensions(
364 y_data,
365 width as usize,
366 width,
367 height,
368 ));
369
370 let cw = (width + 1) / 2;
372 let ch = (height + 1) / 2;
373 let u_data = vec![128u8; (cw * ch) as usize];
374 frame
375 .planes
376 .push(Plane::with_dimensions(u_data, cw as usize, cw, ch));
377
378 let v_data = vec![128u8; (cw * ch) as usize];
380 frame
381 .planes
382 .push(Plane::with_dimensions(v_data, cw as usize, cw, ch));
383
384 frame
385 }
386
387 #[test]
388 #[ignore]
389 fn test_encoder_creation() {
390 let config = make_encoder_config(320, 240);
391 let enc = Ffv1Encoder::new(config).expect("valid config");
392 assert_eq!(enc.codec(), CodecId::Ffv1);
393 }
394
395 #[test]
396 #[ignore]
397 fn test_encoder_invalid_dimensions() {
398 let config = make_encoder_config(0, 240);
399 assert!(Ffv1Encoder::new(config).is_err());
400 }
401
402 #[test]
403 #[ignore]
404 fn test_encoder_extradata() {
405 let config = make_encoder_config(320, 240);
406 let enc = Ffv1Encoder::new(config).expect("valid");
407 let extra = enc.extradata();
408 assert!(extra.len() >= 13);
409 assert_eq!(extra[0], 3); }
411
412 #[test]
413 #[ignore]
414 fn test_encode_single_frame() {
415 let config = make_encoder_config(16, 16);
416 let mut enc = Ffv1Encoder::new(config).expect("valid");
417 let frame = make_test_frame(16, 16);
418
419 enc.send_frame(&frame).expect("encode ok");
420 let packet = enc.receive_packet().expect("ok");
421 assert!(packet.is_some());
422 let pkt = packet.expect("packet");
423 assert!(pkt.keyframe);
424 assert!(!pkt.data.is_empty());
425 }
426
427 #[test]
428 #[ignore]
429 fn test_encode_wrong_dimensions() {
430 let config = make_encoder_config(16, 16);
431 let mut enc = Ffv1Encoder::new(config).expect("valid");
432 let frame = make_test_frame(32, 32); assert!(enc.send_frame(&frame).is_err());
434 }
435
436 #[test]
437 #[ignore]
438 fn test_encoder_flush() {
439 let config = make_encoder_config(16, 16);
440 let mut enc = Ffv1Encoder::new(config).expect("valid");
441 enc.flush().expect("flush ok");
442 let frame = make_test_frame(16, 16);
443 assert!(enc.send_frame(&frame).is_err());
444 }
445
446 #[test]
447 #[ignore]
448 fn test_lossless_roundtrip() {
449 let width = 16u32;
451 let height = 16u32;
452
453 let enc_config = make_encoder_config(width, height);
454 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
455 let frame = make_test_frame(width, height);
456
457 encoder.send_frame(&frame).expect("encode");
458 let packet = encoder.receive_packet().expect("ok").expect("has packet");
459
460 let extradata = encoder.extradata();
462 let mut decoder =
463 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec init");
464
465 decoder
466 .send_packet(&packet.data, packet.pts)
467 .expect("decode");
468 let decoded_frame = decoder.receive_frame().expect("ok").expect("has frame");
469
470 assert_eq!(decoded_frame.planes.len(), frame.planes.len());
472 for (pi, (orig_plane, dec_plane)) in frame
473 .planes
474 .iter()
475 .zip(decoded_frame.planes.iter())
476 .enumerate()
477 {
478 assert_eq!(
479 orig_plane.width, dec_plane.width,
480 "plane {pi} width mismatch"
481 );
482 assert_eq!(
483 orig_plane.height, dec_plane.height,
484 "plane {pi} height mismatch"
485 );
486
487 for y in 0..orig_plane.height as usize {
488 for x in 0..orig_plane.width as usize {
489 let orig_sample = orig_plane.data[y * orig_plane.stride + x];
490 let dec_sample = dec_plane.data[y * dec_plane.stride + x];
491 assert_eq!(
492 orig_sample, dec_sample,
493 "plane {pi} sample mismatch at ({x}, {y}): orig={orig_sample}, decoded={dec_sample}"
494 );
495 }
496 }
497 }
498 }
499
500 #[test]
501 #[ignore]
502 fn test_lossless_roundtrip_constant_frame() {
503 let width = 8u32;
504 let height = 8u32;
505 let enc_config = make_encoder_config(width, height);
506 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
507
508 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
510 frame.timestamp = Timestamp::new(0, Rational::new(1, 1000));
511 let y_data = vec![100u8; (width * height) as usize];
512 frame.planes.push(Plane::with_dimensions(
513 y_data,
514 width as usize,
515 width,
516 height,
517 ));
518 let cw = (width + 1) / 2;
519 let ch = (height + 1) / 2;
520 frame.planes.push(Plane::with_dimensions(
521 vec![128u8; (cw * ch) as usize],
522 cw as usize,
523 cw,
524 ch,
525 ));
526 frame.planes.push(Plane::with_dimensions(
527 vec![128u8; (cw * ch) as usize],
528 cw as usize,
529 cw,
530 ch,
531 ));
532
533 encoder.send_frame(&frame).expect("encode");
534 let packet = encoder.receive_packet().expect("ok").expect("packet");
535
536 let extradata = encoder.extradata();
537 let mut decoder =
538 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec");
539
540 decoder.send_packet(&packet.data, 0).expect("decode");
541 let decoded = decoder.receive_frame().expect("ok").expect("frame");
542
543 for (pi, (orig, dec)) in frame.planes.iter().zip(decoded.planes.iter()).enumerate() {
544 for y in 0..orig.height as usize {
545 for x in 0..orig.width as usize {
546 assert_eq!(
547 orig.data[y * orig.stride + x],
548 dec.data[y * dec.stride + x],
549 "mismatch at plane {pi} ({x}, {y})"
550 );
551 }
552 }
553 }
554 }
555
556 #[test]
557 #[ignore]
558 fn test_lossless_roundtrip_random_pattern() {
559 let width = 32u32;
560 let height = 32u32;
561 let enc_config = make_encoder_config(width, height);
562 let mut encoder = Ffv1Encoder::new(enc_config).expect("enc init");
563
564 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
565 frame.timestamp = Timestamp::new(1000, Rational::new(1, 1000));
566
567 let y_size = (width * height) as usize;
569 let mut y_data = vec![0u8; y_size];
570 for i in 0..y_size {
571 y_data[i] = ((i * 37 + 13) % 256) as u8;
573 }
574 frame.planes.push(Plane::with_dimensions(
575 y_data,
576 width as usize,
577 width,
578 height,
579 ));
580 let cw = (width + 1) / 2;
581 let ch = (height + 1) / 2;
582 let uv_size = (cw * ch) as usize;
583 let mut u_data = vec![0u8; uv_size];
584 let mut v_data = vec![0u8; uv_size];
585 for i in 0..uv_size {
586 u_data[i] = ((i * 53 + 7) % 256) as u8;
587 v_data[i] = ((i * 71 + 23) % 256) as u8;
588 }
589 frame
590 .planes
591 .push(Plane::with_dimensions(u_data, cw as usize, cw, ch));
592 frame
593 .planes
594 .push(Plane::with_dimensions(v_data, cw as usize, cw, ch));
595
596 encoder.send_frame(&frame).expect("encode");
597 let packet = encoder.receive_packet().expect("ok").expect("packet");
598
599 let extradata = encoder.extradata();
600 let mut decoder =
601 super::super::decoder::Ffv1Decoder::with_extradata(&extradata).expect("dec");
602
603 decoder.send_packet(&packet.data, 1000).expect("decode");
604 let decoded = decoder.receive_frame().expect("ok").expect("frame");
605
606 for (pi, (orig, dec)) in frame.planes.iter().zip(decoded.planes.iter()).enumerate() {
607 for y in 0..orig.height as usize {
608 for x in 0..orig.width as usize {
609 assert_eq!(
610 orig.data[y * orig.stride + x],
611 dec.data[y * dec.stride + x],
612 "mismatch at plane {pi} ({x}, {y})"
613 );
614 }
615 }
616 }
617 }
618}