1use super::Encoded;
9use super::backend::{self, Backend};
10use crate::{Color, Error, Frame, Size};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum Codec {
22 #[default]
25 H264,
26 H265,
28}
29
30#[derive(Clone, Debug, Default, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum Kind {
35 #[default]
38 Auto,
39 Hardware,
41 Software,
43 Named(String),
46}
47
48#[derive(Clone, Debug)]
54#[non_exhaustive]
55pub struct Config {
56 pub width: u32,
57 pub height: u32,
58 pub framerate: u32,
59 pub bitrate: Option<u64>,
62 pub gop: u32,
65 pub codec: Codec,
67 pub kind: Kind,
68 pub color: Option<Color>,
76}
77
78impl Config {
79 pub fn new(width: u32, height: u32, framerate: u32) -> Self {
82 Self {
83 width,
84 height,
85 framerate,
86 bitrate: None,
87 gop: framerate.saturating_mul(2).max(1),
89 codec: Codec::default(),
90 kind: Kind::Auto,
91 color: None,
92 }
93 }
94
95 pub fn size(&self) -> Size {
97 Size::new(self.width, self.height)
98 }
99
100 pub(crate) fn resolved_color(&self) -> Color {
105 self.color.unwrap_or_else(|| Color::infer(self.size()))
106 }
107
108 pub(crate) fn resolved_bitrate(&self) -> u64 {
110 self.bitrate.unwrap_or_else(|| {
111 ((self.size().pixels() * self.framerate as u64) as f64 * 0.07) as u64
114 })
115 }
116}
117
118pub struct Encoder {
122 backend: Box<dyn Backend>,
123 codec: Codec,
124 size: Size,
125 bitrate: u64,
126 color: Color,
129 pending_keyframe: bool,
133}
134
135impl Encoder {
136 pub fn new(config: &Config) -> Result<Self, Error> {
138 if config.framerate == 0 {
142 return Err(Error::InvalidFramerate(0));
143 }
144 let size = config.size();
146 size.validate("encoder")?;
147 size.validate_encodable("encoder", config.framerate)?;
148
149 let backend = backend::open(config)?;
150 Ok(Self {
151 backend,
152 codec: config.codec,
153 size,
154 bitrate: config.resolved_bitrate(),
155 color: config.resolved_color(),
156 pending_keyframe: false,
157 })
158 }
159
160 pub fn name(&self) -> &str {
162 self.backend.name()
163 }
164
165 pub fn size(&self) -> Size {
167 self.size
168 }
169
170 pub fn bitrate(&self) -> u64 {
174 self.bitrate
175 }
176
177 pub fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
192 if bitrate == self.bitrate {
193 return Ok(());
194 }
195 self.backend.set_bitrate(bitrate)?;
196 self.bitrate = bitrate;
199 Ok(())
200 }
201
202 pub fn codec(&self) -> Codec {
205 self.codec
206 }
207
208 pub fn keyframe(&mut self) {
220 self.pending_keyframe = true;
221 }
222
223 pub fn encode(&mut self, frame: &Frame) -> Result<Vec<Encoded>, Error> {
238 let size = frame.size();
241 if size != self.size {
242 return Err(Error::Codec(anyhow::anyhow!(
243 "frame {size} does not match encoder {}",
244 self.size
245 )));
246 }
247 if let Some(color) = frame.surface.color()
257 && color != self.color
258 {
259 static WARN_ONCE: std::sync::Once = std::sync::Once::new();
260 WARN_ONCE.call_once(|| {
261 tracing::warn!(
262 frame = ?color,
263 encoder = ?self.color,
264 "frame color space differs from the one written into the bitstream; set encode::Config::color"
265 );
266 });
267 }
268 let encoded = self.backend.encode(frame, self.pending_keyframe)?;
269 self.pending_keyframe = false;
273 Ok(encoded)
274 }
275
276 pub fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
291 self.backend.flush()
292 }
293
294 pub fn finish(mut self) -> Result<Vec<Encoded>, Error> {
301 self.backend.finish()
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 use crate::{I420, Surface};
310
311 fn gray_rgba(width: u32, height: u32) -> Vec<u8> {
313 vec![0x80u8; width as usize * height as usize * 4]
314 }
315
316 fn gray_frame(width: u32, height: u32, index: u64) -> Frame {
319 let surface = Surface::rgba(&gray_rgba(width, height), Size::new(width, height)).unwrap();
320 Frame::new(surface, at(index))
321 }
322
323 fn at(index: u64) -> moq_net::Timestamp {
325 moq_net::Timestamp::from_micros(index * 33_333).unwrap()
326 }
327
328 fn payloads(frames: &[Encoded]) -> Vec<bytes::Bytes> {
330 frames.iter().map(|f| f.payload.clone()).collect()
331 }
332
333 #[test]
334 fn software_encoder_emits_annexb() {
335 let config = Config {
336 kind: Kind::Software,
337 ..Config::new(320, 240, 30)
338 };
339 let mut encoder = Encoder::new(&config).expect("openh264 is vendored, always available");
340 assert_eq!(encoder.name(), "openh264");
341
342 let mut frames = Vec::new();
343 for i in 0..30 {
344 if i == 0 {
345 encoder.keyframe();
346 }
347 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
348 }
349 frames.extend(encoder.finish().unwrap());
350
351 assert!(!frames.is_empty(), "encoder produced no packets");
352
353 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
356 assert!(
357 micros.windows(2).all(|w| w[0] < w[1]),
358 "encoded timestamps not strictly increasing: {micros:?}"
359 );
360 assert!(
361 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
362 "encoded timestamp outside the fed set: {micros:?}"
363 );
364
365 let packets = payloads(&frames);
368 let first = &packets[0];
369 let has_start_code = first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]);
370 assert!(
371 has_start_code,
372 "first packet is not Annex-B: {:02x?}",
373 &first[..first.len().min(8)]
374 );
375 }
376
377 #[test]
379 fn encode_rgba_surface_emits_annexb() {
380 let config = Config {
381 kind: Kind::Software,
382 ..Config::new(320, 240, 30)
383 };
384 let mut encoder = Encoder::new(&config).unwrap();
385
386 let mut frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
387 frames.extend(encoder.finish().unwrap());
388 assert!(!frames.is_empty());
389 let packets = payloads(&frames);
390 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
391 }
392
393 #[test]
395 fn encode_i420_surface_emits_annexb() {
396 let config = Config {
397 kind: Kind::Software,
398 ..Config::new(320, 240, 30)
399 };
400 let mut encoder = Encoder::new(&config).unwrap();
401
402 let i420 = I420::new(320, 240, vec![0x80u8; I420::len(320, 240)]).unwrap();
404 let frame = Frame::new(Surface::I420(i420), at(0));
405 let mut frames = encoder.encode(&frame).unwrap();
406 frames.extend(encoder.finish().unwrap());
407 assert!(!frames.is_empty());
408 let packets = payloads(&frames);
409 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
410 }
411
412 #[test]
415 fn encode_rejects_dimension_mismatch() {
416 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
417 return;
418 };
419 assert!(matches!(encoder.encode(&gray_frame(640, 480, 0)), Err(Error::Codec(_))));
420 }
421
422 #[test]
426 fn encode_rejects_transposed_frame() {
427 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
428 return;
429 };
430
431 let transposed = gray_frame(240, 320, 0);
432 assert_eq!(
433 gray_rgba(240, 320).len(),
434 gray_rgba(320, 240).len(),
435 "the byte counts must collide"
436 );
437 assert!(matches!(encoder.encode(&transposed), Err(Error::Codec(_))));
438 }
439
440 #[test]
441 fn new_rejects_zero_framerate() {
442 let config = Config::new(320, 240, 0);
445 assert!(matches!(Encoder::new(&config), Err(Error::InvalidFramerate(0))));
446 }
447
448 #[test]
449 fn unknown_named_encoder_errors() {
450 let config = Config {
451 kind: Kind::Named("definitely_not_a_codec".into()),
452 ..Config::new(320, 240, 30)
453 };
454 assert!(matches!(Encoder::new(&config), Err(Error::NoEncoder(_))));
455 }
456
457 #[cfg(target_os = "macos")]
461 #[test]
462 fn videotoolbox_emits_annexb_keyframe() {
463 let config = Config {
464 kind: Kind::Named("videotoolbox".into()),
465 ..Config::new(320, 240, 30)
466 };
467 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
468 assert_eq!(encoder.name(), "videotoolbox");
469
470 let mut frames = Vec::new();
471 for i in 0..10 {
472 if i == 0 {
473 encoder.keyframe();
474 }
475 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
476 }
477 frames.extend(encoder.finish().unwrap());
478
479 assert!(!frames.is_empty(), "encoder produced no packets");
480 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
483 assert!(
484 micros.windows(2).all(|w| w[0] < w[1]),
485 "encoded timestamps not strictly increasing: {micros:?}"
486 );
487
488 let packets = payloads(&frames);
489 let first = &packets[0];
490 assert!(
491 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
492 "first packet is not Annex-B"
493 );
494
495 let types = nal_types(first);
498 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
499 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
500 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
501 }
502
503 #[cfg(target_os = "macos")]
507 #[test]
508 fn videotoolbox_emits_annexb_keyframe_h265() {
509 let config = Config {
510 codec: Codec::H265,
511 kind: Kind::Named("videotoolbox".into()),
512 ..Config::new(320, 240, 30)
513 };
514 let mut encoder = Encoder::new(&config).expect("videotoolbox HEVC is available on macOS");
515 assert_eq!(encoder.name(), "videotoolbox");
516 assert_eq!(encoder.codec(), Codec::H265);
517
518 let mut frames = Vec::new();
519 for i in 0..10 {
520 if i == 0 {
521 encoder.keyframe();
522 }
523 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
524 }
525 frames.extend(encoder.finish().unwrap());
526
527 assert!(!frames.is_empty(), "encoder produced no packets");
528 let packets = payloads(&frames);
529 let first = &packets[0];
530 assert!(
531 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
532 "first packet is not Annex-B"
533 );
534
535 let types = hevc_nal_types(first);
538 assert!(types.contains(&32), "no VPS in first packet: {types:?}");
539 assert!(types.contains(&33), "no SPS in first packet: {types:?}");
540 assert!(types.contains(&34), "no PPS in first packet: {types:?}");
541 assert!(
542 types.iter().any(|t| (16..=23).contains(t)),
543 "first packet is not an IRAP: {types:?}"
544 );
545 }
546
547 #[cfg(target_os = "macos")]
549 fn hevc_nal_types(annexb: &[u8]) -> Vec<u8> {
550 let mut types = Vec::new();
551 let mut i = 0;
552 while i + 3 < annexb.len() {
553 if annexb[i..i + 3] == [0, 0, 1] {
554 types.push((annexb[i + 3] >> 1) & 0x3f);
555 i += 3;
556 } else {
557 i += 1;
558 }
559 }
560 types
561 }
562
563 #[cfg(target_os = "macos")]
566 #[test]
567 fn videotoolbox_encodes_surface_zero_copy() {
568 let config = Config {
569 kind: Kind::Named("videotoolbox".into()),
570 ..Config::new(320, 240, 30)
571 };
572 let mut encoder = Encoder::new(&config).unwrap();
573
574 let mut frames = Vec::new();
575 for i in 0..10 {
576 if i == 0 {
577 encoder.keyframe();
578 }
579 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(i));
580 frames.extend(encoder.encode(&frame).unwrap());
581 }
582 frames.extend(encoder.finish().unwrap());
583
584 assert!(!frames.is_empty());
585 let packets = payloads(&frames);
586 let types = nal_types(&packets[0]);
587 assert!(
588 types.contains(&7) && types.contains(&8) && types.contains(&5),
589 "no IDR: {types:?}"
590 );
591 }
592
593 #[cfg(target_os = "macos")]
596 #[test]
597 fn openh264_downloads_surface() {
598 let config = Config {
599 kind: Kind::Software,
600 ..Config::new(320, 240, 30)
601 };
602 let mut encoder = Encoder::new(&config).unwrap();
603
604 encoder.keyframe();
605 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(0));
606 let mut frames = encoder.encode(&frame).unwrap();
607 frames.extend(encoder.finish().unwrap());
608
609 assert!(!frames.is_empty());
610 let packets = payloads(&frames);
611 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
612 }
613
614 #[cfg(target_os = "macos")]
617 fn nv12_surface(width: u32, height: u32) -> crate::frame::macos::PixelBuffer {
618 use std::ptr::{self, NonNull};
619
620 use objc2_core_foundation::CFRetained;
621 use objc2_core_video::{
622 CVPixelBuffer, CVPixelBufferCreate, CVPixelBufferGetBaseAddressOfPlane, CVPixelBufferGetBytesPerRowOfPlane,
623 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
624 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
625 };
626
627 let mut raw: *mut CVPixelBuffer = ptr::null_mut();
628 let status = unsafe {
629 CVPixelBufferCreate(
630 None,
631 width as usize,
632 height as usize,
633 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
634 None,
635 NonNull::new(&mut raw).unwrap(),
636 )
637 };
638 assert_eq!(status, 0, "CVPixelBufferCreate failed");
639 let buffer = unsafe { CFRetained::from_raw(NonNull::new(raw).unwrap()) };
640
641 let flags = CVPixelBufferLockFlags(0);
642 assert_eq!(unsafe { CVPixelBufferLockBaseAddress(&buffer, flags) }, 0);
643 for (plane, rows) in [(0usize, height as usize), (1usize, height as usize / 2)] {
644 let base = CVPixelBufferGetBaseAddressOfPlane(&buffer, plane) as *mut u8;
645 let stride = CVPixelBufferGetBytesPerRowOfPlane(&buffer, plane);
646 unsafe { ptr::write_bytes(base, 128, stride * rows) };
647 }
648 unsafe { CVPixelBufferUnlockBaseAddress(&buffer, flags) };
649
650 crate::frame::macos::PixelBuffer::new(buffer, width, height)
651 }
652
653 fn nal_types(annexb: &[u8]) -> Vec<u8> {
656 let mut types = Vec::new();
657 let mut i = 0;
658 while i + 3 < annexb.len() {
659 if annexb[i..i + 3] == [0, 0, 1] {
660 types.push(annexb[i + 3] & 0x1f);
661 i += 3;
662 } else {
663 i += 1;
664 }
665 }
666 types
667 }
668
669 #[cfg(target_os = "windows")]
673 #[test]
674 #[ignore]
675 fn mediafoundation_cpu_rgba() {
676 let config = Config {
677 kind: Kind::Named("mediafoundation".into()),
678 ..Config::new(640, 480, 30)
679 };
680 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
681 assert_eq!(encoder.name(), "mediafoundation");
682
683 let mut frames = Vec::new();
684 for i in 0..30 {
685 if i == 0 {
686 encoder.keyframe();
687 }
688 frames.extend(encoder.encode(&gray_frame(640, 480, i)).unwrap());
689 }
690 frames.extend(encoder.finish().unwrap());
691
692 assert!(!frames.is_empty(), "encoder produced no packets");
693 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
696 assert!(
697 micros.windows(2).all(|w| w[0] < w[1]),
698 "encoded timestamps not strictly increasing: {micros:?}"
699 );
700 assert!(
701 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
702 "encoded timestamp outside the fed set: {micros:?}"
703 );
704
705 let packets = payloads(&frames);
706 let types = nal_types(&packets[0]);
707 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
708 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
709 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
710 }
711
712 #[cfg(target_os = "windows")]
716 #[tokio::test]
717 #[ignore]
718 async fn mediafoundation_camera_texture() {
719 let mut camera = crate::capture::open(&crate::capture::Config::default())
720 .await
721 .expect("open default camera");
722 let (w, h) = (camera.width(), camera.height());
723
724 let config = Config {
725 kind: Kind::Named("mediafoundation".into()),
726 ..Config::new(w, h, camera.framerate().unwrap_or(30))
727 };
728 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
729
730 let mut frames = Vec::new();
731 let mut textures = 0;
732 for i in 0..30 {
733 let surface = camera.read().await.expect("frame, not end of stream");
734 if matches!(surface, Surface::Texture(_)) {
735 textures += 1;
736 }
737 if i == 0 {
738 encoder.keyframe();
739 }
740 frames.extend(encoder.encode(&Frame::new(surface, at(i))).unwrap());
741 }
742 frames.extend(encoder.finish().unwrap());
743
744 assert!(textures > 0, "capture never produced a GPU texture");
747 assert!(!frames.is_empty(), "encoder produced no packets");
748 let packets = payloads(&frames);
749 let types = nal_types(&packets[0]);
750 assert!(
751 types.contains(&7) && types.contains(&8) && types.contains(&5),
752 "no IDR: {types:?}"
753 );
754 }
755
756 #[test]
760 fn set_bitrate_retunes_software_encoder() {
761 let config = Config {
762 kind: Kind::Software,
763 ..Config::new(320, 240, 30)
764 };
765 let mut encoder = Encoder::new(&config).unwrap();
766
767 let opened = encoder.bitrate();
768 assert_eq!(opened, config.resolved_bitrate());
769
770 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
772
773 let halved = opened / 2;
774 encoder.set_bitrate(halved).unwrap();
775 assert_eq!(encoder.bitrate(), halved);
776
777 let frames = encoder.encode(&gray_frame(320, 240, 1)).unwrap();
779 assert!(!frames.is_empty(), "encoder produced nothing after a retune");
780 let packets = payloads(&frames);
781 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
782 }
783
784 #[test]
788 fn set_bitrate_before_the_first_frame_is_deferred() {
789 let config = Config {
790 kind: Kind::Software,
791 ..Config::new(320, 240, 30)
792 };
793 let mut encoder = Encoder::new(&config).unwrap();
794
795 let halved = encoder.bitrate() / 2;
796 encoder.set_bitrate(halved).expect("a retune before the first frame");
797 assert_eq!(encoder.bitrate(), halved);
798
799 let frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
801 assert!(!frames.is_empty());
802
803 encoder.set_bitrate(halved / 2).unwrap();
805 assert!(encoder.encode(&gray_frame(320, 240, 1)).is_ok());
806 }
807
808 #[test]
811 fn set_bitrate_to_current_is_a_noop() {
812 let config = Config {
813 kind: Kind::Software,
814 ..Config::new(320, 240, 30)
815 };
816 let mut encoder = Encoder::new(&config).unwrap();
817
818 let opened = encoder.bitrate();
819 encoder.set_bitrate(opened).unwrap();
820 assert_eq!(encoder.bitrate(), opened);
821 }
822
823 #[test]
824 fn default_bitrate_scales_with_resolution() {
825 let small = Config::new(320, 240, 30).resolved_bitrate();
826 let large = Config::new(1920, 1080, 30).resolved_bitrate();
827 assert!(large > small);
828 assert!(small > 0);
829 }
830
831 struct Delayed {
836 pending: Option<Encoded>,
837 }
838
839 impl Backend for Delayed {
840 fn encode(&mut self, frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
841 let payload = bytes::Bytes::from(frame.timestamp.as_micros().to_string());
842 let previous = self.pending.replace(Encoded::new(payload, frame.timestamp));
843 Ok(previous.into_iter().collect())
844 }
845
846 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
847 Ok(self.pending.take().into_iter().collect())
848 }
849
850 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
851 self.flush()
852 }
853
854 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
855 Ok(())
856 }
857
858 fn name(&self) -> &str {
859 "delayed"
860 }
861 }
862
863 fn encoder_with(backend: Box<dyn Backend>, config: &Config) -> Encoder {
866 Encoder {
867 backend,
868 codec: config.codec,
869 size: config.size(),
870 bitrate: config.resolved_bitrate(),
871 color: config.resolved_color(),
872 pending_keyframe: false,
873 }
874 }
875
876 struct Recorder(std::sync::Arc<std::sync::Mutex<Vec<bool>>>);
879
880 impl Backend for Recorder {
881 fn encode(&mut self, _frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error> {
882 self.0.lock().unwrap().push(keyframe);
883 Ok(Vec::new())
884 }
885
886 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
887 Ok(Vec::new())
888 }
889
890 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
891 Ok(Vec::new())
892 }
893
894 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
895 Ok(())
896 }
897
898 fn name(&self) -> &str {
899 "recorder"
900 }
901 }
902
903 #[test]
908 fn a_keyframe_request_waits_for_the_next_frame_then_clears() {
909 let log = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
910 let config = Config::new(320, 240, 30);
911 let mut encoder = encoder_with(Box::new(Recorder(log.clone())), &config);
912
913 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
915
916 encoder.keyframe();
918 encoder.keyframe();
919 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
920
921 encoder.encode(&gray_frame(320, 240, 2)).unwrap();
923
924 assert_eq!(*log.lock().unwrap(), vec![false, true, false]);
925 }
926
927 #[test]
932 fn a_mid_stream_keyframe_request_emits_an_idr() {
933 let config = Config {
934 kind: Kind::Software,
935 ..Config::new(320, 240, 30)
936 };
937 let mut encoder = Encoder::new(&Config { gop: 1000, ..config }).unwrap();
940
941 let mut per_frame = Vec::new();
942 for i in 0..6 {
943 if i == 3 {
945 encoder.keyframe();
946 }
947 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
948 let joined: Vec<u8> = encoded.iter().flat_map(|f| f.payload.iter()).copied().collect();
949 per_frame.push(nal_types(&joined));
950 }
951
952 let asked = &per_frame[3];
955 assert!(asked.contains(&5), "the requested frame is not an IDR: {asked:?}");
956 assert!(asked.contains(&7), "no SPS with the requested IDR: {asked:?}");
957 assert!(asked.contains(&8), "no PPS with the requested IDR: {asked:?}");
958
959 for i in [1, 2, 4, 5] {
963 assert!(
964 !per_frame[i].contains(&5),
965 "frame {i} was keyed without being asked: {:?}",
966 per_frame[i]
967 );
968 }
969 }
970
971 struct Failing;
973
974 impl Backend for Failing {
975 fn encode(&mut self, _frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
976 Err(Error::Codec(anyhow::anyhow!("no")))
977 }
978
979 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
980 Ok(Vec::new())
981 }
982
983 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
984 Ok(Vec::new())
985 }
986
987 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
988 Ok(())
989 }
990
991 fn name(&self) -> &str {
992 "failing"
993 }
994 }
995
996 #[test]
1001 fn a_failed_encode_keeps_the_keyframe_request() {
1002 let config = Config::new(320, 240, 30);
1003
1004 let mut encoder = encoder_with(Box::new(Failing), &config);
1005 encoder.keyframe();
1006 assert!(encoder.encode(&gray_frame(320, 240, 0)).is_err());
1007 assert!(encoder.pending_keyframe, "the backend error swallowed the request");
1008
1009 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1011 encoder.keyframe();
1012 assert!(encoder.encode(&gray_frame(640, 480, 0)).is_err());
1013 assert!(encoder.pending_keyframe, "the size check swallowed the request");
1014
1015 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
1017 assert!(!encoder.pending_keyframe);
1018 }
1019
1020 #[test]
1026 fn a_buffering_backend_keeps_each_frames_timestamp() {
1027 let config = Config::new(320, 240, 30);
1028 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1029
1030 for i in 0..5 {
1034 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1035 if i == 0 {
1036 assert!(encoded.is_empty(), "the first frame is still buffered");
1037 continue;
1038 }
1039 assert_eq!(encoded.len(), 1);
1040 assert_eq!(encoded[0].timestamp, at(i - 1));
1041 assert_eq!(&encoded[0].payload[..], at(i - 1).as_micros().to_string().as_bytes());
1044 }
1045
1046 let tail = encoder.finish().unwrap();
1049 assert_eq!(tail.len(), 1);
1050 assert_eq!(tail[0].timestamp, at(4));
1051 assert!(
1052 encoder_with(Box::new(Delayed { pending: None }), &config)
1053 .finish()
1054 .unwrap()
1055 .is_empty()
1056 );
1057 }
1058
1059 #[test]
1067 fn a_flush_empties_a_pipelined_backend_and_leaves_it_running() {
1068 let config = Config::new(320, 240, 30);
1069 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1070
1071 let mut group = Vec::new();
1072 for i in 0..3 {
1073 group.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
1074 }
1075 group.extend(encoder.flush().unwrap());
1076
1077 let times: Vec<_> = group.iter().map(|packet| packet.timestamp).collect();
1079 assert_eq!(times, vec![at(0), at(1), at(2)], "the group lost or reordered frames");
1080
1081 let mut next = encoder.encode(&gray_frame(320, 240, 3)).unwrap();
1084 assert!(next.is_empty(), "frame 3 is buffered, so nothing comes back yet");
1085 next.extend(encoder.finish().unwrap());
1086 let times: Vec<_> = next.iter().map(|packet| packet.timestamp).collect();
1087 assert_eq!(times, vec![at(3)], "the flush left something behind");
1088 }
1089
1090 #[cfg(target_os = "macos")]
1095 #[test]
1096 fn videotoolbox_sps_declares_the_color_space() {
1097 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1098
1099 for (size, described) in [
1100 (Size::new(640, 480), BT601_DESCRIBED),
1101 (Size::new(1920, 1080), BT709_DESCRIBED),
1102 ] {
1103 let config = Config {
1104 kind: Kind::Named("videotoolbox".into()),
1105 ..Config::new(size.width, size.height, 30)
1106 };
1107 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
1108
1109 let rgba = [255u8, 0, 0, 255].repeat(size.pixels() as usize);
1110 let surface = crate::frame::Surface::rgba(&rgba, size).unwrap();
1111 encoder.keyframe();
1112 let frames = encoder
1113 .encode(&Frame::new(surface, moq_net::Timestamp::from_micros(0).unwrap()))
1114 .unwrap();
1115
1116 let keyframe = frames.first().expect("a keyframe");
1117 assert_eq!(declared_color(&keyframe.payload), Some(described), "{size} SPS");
1118 }
1119 }
1120
1121 #[test]
1126 fn config_color_pins_the_space_a_resize_carried() {
1127 use crate::Color;
1128
1129 let big = Size::new(1280, 720);
1130 let small = Size::new(640, 480);
1131
1132 let rgba = vec![0x80u8; big.pixels() as usize * 4];
1134 let frame = Frame::new(
1135 crate::frame::Surface::rgba(&rgba, big).unwrap(),
1136 moq_net::Timestamp::from_micros(0).unwrap(),
1137 );
1138 let scaled = frame.resize(small).unwrap();
1139 assert_eq!(
1140 scaled.surface.color(),
1141 Some(Color::Bt709Limited),
1142 "resize keeps the space"
1143 );
1144
1145 let config = Config {
1149 kind: Kind::Software,
1150 ..Config::new(small.width, small.height, 30)
1151 };
1152 let mut encoder = Encoder::new(&config).unwrap();
1153 encoder.keyframe();
1154 let frames = encoder.encode(&scaled).expect("a mismatch warns rather than fails");
1155 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1156 assert_eq!(
1157 declared_color(&frames.first().expect("a keyframe").payload),
1158 Some(BT601_DESCRIBED),
1159 "the inferred label is the wrong one, which is the case Config::color covers"
1160 );
1161
1162 let config = Config {
1164 kind: Kind::Software,
1165 color: Some(Color::Bt709Limited),
1166 ..Config::new(small.width, small.height, 30)
1167 };
1168 let mut encoder = Encoder::new(&config).unwrap();
1169 encoder.keyframe();
1170 let frames = encoder.encode(&scaled).expect("a declared space encodes");
1171
1172 let keyframe = frames.first().expect("a keyframe");
1173 assert_eq!(declared_color(&keyframe.payload), Some(BT709_DESCRIBED));
1174 }
1175}