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