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 async fn probe(&self) -> Result<hang::catalog::VideoConfig, Error> {
121 let mut sink = super::Sink::open(self).await?;
124
125 let size = self.size();
127 let i420 = crate::I420::new(
128 size.width,
129 size.height,
130 vec![0x80u8; crate::I420::len(size.width, size.height)],
131 )?;
132 let frame = Frame::new(crate::Surface::I420(i420), moq_net::Timestamp::from_micros(0)?);
133
134 sink.keyframe();
135 let mut encoded = sink.encode(frame).await?;
136 if encoded.is_empty() {
138 encoded = sink.flush().await?;
139 }
140
141 let annexb: Vec<u8> = encoded.iter().flat_map(|frame| frame.payload.iter().copied()).collect();
142 let parsed = match self.codec {
143 Codec::H264 => moq_mux::codec::h264::config(&annexb),
144 Codec::H265 => moq_mux::codec::h265::config(&annexb),
145 };
146 let mut rendition = parsed.map_err(|err| {
147 Error::Codec(anyhow::anyhow!(
148 "{} emitted no usable parameter sets: {err}",
149 sink.name()
150 ))
151 })?;
152
153 rendition.bitrate.get_or_insert(self.resolved_bitrate());
156 rendition.framerate.get_or_insert(self.framerate.into());
157 Ok(rendition)
158 }
159
160 pub(crate) fn resolved_color(&self) -> Color {
165 self.color.unwrap_or_else(|| Color::infer(self.size()))
166 }
167
168 pub(crate) fn resolved_bitrate(&self) -> u64 {
170 self.bitrate
171 .unwrap_or_else(|| default_bitrate(self.size(), self.framerate))
172 }
173}
174
175pub(crate) fn default_bitrate(size: Size, framerate: u32) -> u64 {
178 ((size.pixels() * framerate as u64) as f64 * 0.07) as u64
179}
180
181pub struct Encoder {
185 backend: Box<dyn Backend>,
186 codec: Codec,
187 size: Size,
188 bitrate: u64,
189 color: Color,
192 pending_keyframe: bool,
196}
197
198impl Encoder {
199 pub fn new(config: &Config) -> Result<Self, Error> {
201 if config.framerate == 0 {
205 return Err(Error::InvalidFramerate(0));
206 }
207 let size = config.size();
209 size.validate("encoder")?;
210 size.validate_encodable("encoder", config.framerate)?;
211
212 let backend = backend::open(config)?;
213 Ok(Self {
214 backend,
215 codec: config.codec,
216 size,
217 bitrate: config.resolved_bitrate(),
218 color: config.resolved_color(),
219 pending_keyframe: false,
220 })
221 }
222
223 pub fn name(&self) -> &str {
225 self.backend.name()
226 }
227
228 pub fn size(&self) -> Size {
230 self.size
231 }
232
233 pub fn bitrate(&self) -> u64 {
237 self.bitrate
238 }
239
240 pub fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
255 if bitrate == self.bitrate {
256 return Ok(());
257 }
258 self.backend.set_bitrate(bitrate)?;
259 self.bitrate = bitrate;
262 Ok(())
263 }
264
265 pub fn codec(&self) -> Codec {
268 self.codec
269 }
270
271 pub fn keyframe(&mut self) {
283 self.pending_keyframe = true;
284 }
285
286 pub fn encode(&mut self, frame: &Frame) -> Result<Vec<Encoded>, Error> {
301 let size = frame.size();
304 if size != self.size {
305 return Err(Error::Codec(anyhow::anyhow!(
306 "frame {size} does not match encoder {}",
307 self.size
308 )));
309 }
310 if let Some(color) = frame.surface.color()
320 && color != self.color
321 {
322 static WARN_ONCE: std::sync::Once = std::sync::Once::new();
323 WARN_ONCE.call_once(|| {
324 tracing::warn!(
325 frame = ?color,
326 encoder = ?self.color,
327 "frame color space differs from the one written into the bitstream; set encode::Config::color"
328 );
329 });
330 }
331 let encoded = self.backend.encode(frame, self.pending_keyframe)?;
332 self.pending_keyframe = false;
336 Ok(encoded)
337 }
338
339 pub fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
354 self.backend.flush()
355 }
356
357 pub fn finish(mut self) -> Result<Vec<Encoded>, Error> {
364 self.backend.finish()
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 use crate::{I420, Surface};
373
374 fn gray_rgba(width: u32, height: u32) -> Vec<u8> {
376 vec![0x80u8; width as usize * height as usize * 4]
377 }
378
379 fn gray_frame(width: u32, height: u32, index: u64) -> Frame {
382 let surface = Surface::rgba(&gray_rgba(width, height), Size::new(width, height)).unwrap();
383 Frame::new(surface, at(index))
384 }
385
386 fn at(index: u64) -> moq_net::Timestamp {
388 moq_net::Timestamp::from_micros(index * 33_333).unwrap()
389 }
390
391 fn payloads(frames: &[Encoded]) -> Vec<bytes::Bytes> {
393 frames.iter().map(|f| f.payload.clone()).collect()
394 }
395
396 #[test]
397 fn software_encoder_emits_annexb() {
398 let config = Config {
399 kind: Kind::Software,
400 ..Config::new(320, 240, 30)
401 };
402 let mut encoder = Encoder::new(&config).expect("openh264 is vendored, always available");
403 assert_eq!(encoder.name(), "openh264");
404
405 let mut frames = Vec::new();
406 for i in 0..30 {
407 if i == 0 {
408 encoder.keyframe();
409 }
410 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
411 }
412 frames.extend(encoder.finish().unwrap());
413
414 assert!(!frames.is_empty(), "encoder produced no packets");
415
416 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
419 assert!(
420 micros.windows(2).all(|w| w[0] < w[1]),
421 "encoded timestamps not strictly increasing: {micros:?}"
422 );
423 assert!(
424 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
425 "encoded timestamp outside the fed set: {micros:?}"
426 );
427
428 let packets = payloads(&frames);
431 let first = &packets[0];
432 let has_start_code = first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]);
433 assert!(
434 has_start_code,
435 "first packet is not Annex-B: {:02x?}",
436 &first[..first.len().min(8)]
437 );
438 }
439
440 #[test]
442 fn encode_rgba_surface_emits_annexb() {
443 let config = Config {
444 kind: Kind::Software,
445 ..Config::new(320, 240, 30)
446 };
447 let mut encoder = Encoder::new(&config).unwrap();
448
449 let mut frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
450 frames.extend(encoder.finish().unwrap());
451 assert!(!frames.is_empty());
452 let packets = payloads(&frames);
453 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
454 }
455
456 #[test]
458 fn encode_i420_surface_emits_annexb() {
459 let config = Config {
460 kind: Kind::Software,
461 ..Config::new(320, 240, 30)
462 };
463 let mut encoder = Encoder::new(&config).unwrap();
464
465 let i420 = I420::new(320, 240, vec![0x80u8; I420::len(320, 240)]).unwrap();
467 let frame = Frame::new(Surface::I420(i420), at(0));
468 let mut frames = encoder.encode(&frame).unwrap();
469 frames.extend(encoder.finish().unwrap());
470 assert!(!frames.is_empty());
471 let packets = payloads(&frames);
472 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
473 }
474
475 #[test]
478 fn encode_rejects_dimension_mismatch() {
479 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
480 return;
481 };
482 assert!(matches!(encoder.encode(&gray_frame(640, 480, 0)), Err(Error::Codec(_))));
483 }
484
485 #[test]
489 fn encode_rejects_transposed_frame() {
490 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
491 return;
492 };
493
494 let transposed = gray_frame(240, 320, 0);
495 assert_eq!(
496 gray_rgba(240, 320).len(),
497 gray_rgba(320, 240).len(),
498 "the byte counts must collide"
499 );
500 assert!(matches!(encoder.encode(&transposed), Err(Error::Codec(_))));
501 }
502
503 #[test]
504 fn new_rejects_zero_framerate() {
505 let config = Config::new(320, 240, 0);
508 assert!(matches!(Encoder::new(&config), Err(Error::InvalidFramerate(0))));
509 }
510
511 #[test]
512 fn unknown_named_encoder_errors() {
513 let config = Config {
514 kind: Kind::Named("definitely_not_a_codec".into()),
515 ..Config::new(320, 240, 30)
516 };
517 assert!(matches!(Encoder::new(&config), Err(Error::NoEncoder(_))));
518 }
519
520 #[cfg(target_os = "macos")]
524 #[test]
525 fn videotoolbox_emits_annexb_keyframe() {
526 let config = Config {
527 kind: Kind::Named("videotoolbox".into()),
528 ..Config::new(320, 240, 30)
529 };
530 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
531 assert_eq!(encoder.name(), "videotoolbox");
532
533 let mut frames = Vec::new();
534 for i in 0..10 {
535 if i == 0 {
536 encoder.keyframe();
537 }
538 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
539 }
540 frames.extend(encoder.finish().unwrap());
541
542 assert!(!frames.is_empty(), "encoder produced no packets");
543 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
546 assert!(
547 micros.windows(2).all(|w| w[0] < w[1]),
548 "encoded timestamps not strictly increasing: {micros:?}"
549 );
550
551 let packets = payloads(&frames);
552 let first = &packets[0];
553 assert!(
554 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
555 "first packet is not Annex-B"
556 );
557
558 let types = nal_types(first);
561 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
562 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
563 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
564 }
565
566 #[cfg(target_os = "macos")]
570 #[test]
571 fn videotoolbox_emits_annexb_keyframe_h265() {
572 let config = Config {
573 codec: Codec::H265,
574 kind: Kind::Named("videotoolbox".into()),
575 ..Config::new(320, 240, 30)
576 };
577 let mut encoder = Encoder::new(&config).expect("videotoolbox HEVC is available on macOS");
578 assert_eq!(encoder.name(), "videotoolbox");
579 assert_eq!(encoder.codec(), Codec::H265);
580
581 let mut frames = Vec::new();
582 for i in 0..10 {
583 if i == 0 {
584 encoder.keyframe();
585 }
586 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
587 }
588 frames.extend(encoder.finish().unwrap());
589
590 assert!(!frames.is_empty(), "encoder produced no packets");
591 let packets = payloads(&frames);
592 let first = &packets[0];
593 assert!(
594 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
595 "first packet is not Annex-B"
596 );
597
598 let types = hevc_nal_types(first);
601 assert!(types.contains(&32), "no VPS in first packet: {types:?}");
602 assert!(types.contains(&33), "no SPS in first packet: {types:?}");
603 assert!(types.contains(&34), "no PPS in first packet: {types:?}");
604 assert!(
605 types.iter().any(|t| (16..=23).contains(t)),
606 "first packet is not an IRAP: {types:?}"
607 );
608 }
609
610 #[cfg(target_os = "macos")]
612 fn hevc_nal_types(annexb: &[u8]) -> Vec<u8> {
613 let mut types = Vec::new();
614 let mut i = 0;
615 while i + 3 < annexb.len() {
616 if annexb[i..i + 3] == [0, 0, 1] {
617 types.push((annexb[i + 3] >> 1) & 0x3f);
618 i += 3;
619 } else {
620 i += 1;
621 }
622 }
623 types
624 }
625
626 #[cfg(target_os = "macos")]
629 #[test]
630 fn videotoolbox_encodes_surface_zero_copy() {
631 let config = Config {
632 kind: Kind::Named("videotoolbox".into()),
633 ..Config::new(320, 240, 30)
634 };
635 let mut encoder = Encoder::new(&config).unwrap();
636
637 let mut frames = Vec::new();
638 for i in 0..10 {
639 if i == 0 {
640 encoder.keyframe();
641 }
642 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(i));
643 frames.extend(encoder.encode(&frame).unwrap());
644 }
645 frames.extend(encoder.finish().unwrap());
646
647 assert!(!frames.is_empty());
648 let packets = payloads(&frames);
649 let types = nal_types(&packets[0]);
650 assert!(
651 types.contains(&7) && types.contains(&8) && types.contains(&5),
652 "no IDR: {types:?}"
653 );
654 }
655
656 #[cfg(target_os = "macos")]
659 #[test]
660 fn openh264_downloads_surface() {
661 let config = Config {
662 kind: Kind::Software,
663 ..Config::new(320, 240, 30)
664 };
665 let mut encoder = Encoder::new(&config).unwrap();
666
667 encoder.keyframe();
668 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(0));
669 let mut frames = encoder.encode(&frame).unwrap();
670 frames.extend(encoder.finish().unwrap());
671
672 assert!(!frames.is_empty());
673 let packets = payloads(&frames);
674 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
675 }
676
677 #[cfg(target_os = "macos")]
680 fn nv12_surface(width: u32, height: u32) -> crate::frame::macos::PixelBuffer {
681 use std::ptr::{self, NonNull};
682
683 use objc2_core_foundation::CFRetained;
684 use objc2_core_video::{
685 CVPixelBuffer, CVPixelBufferCreate, CVPixelBufferGetBaseAddressOfPlane, CVPixelBufferGetBytesPerRowOfPlane,
686 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
687 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
688 };
689
690 let mut raw: *mut CVPixelBuffer = ptr::null_mut();
691 let status = unsafe {
692 CVPixelBufferCreate(
693 None,
694 width as usize,
695 height as usize,
696 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
697 None,
698 NonNull::new(&mut raw).unwrap(),
699 )
700 };
701 assert_eq!(status, 0, "CVPixelBufferCreate failed");
702 let buffer = unsafe { CFRetained::from_raw(NonNull::new(raw).unwrap()) };
703
704 let flags = CVPixelBufferLockFlags(0);
705 assert_eq!(unsafe { CVPixelBufferLockBaseAddress(&buffer, flags) }, 0);
706 for (plane, rows) in [(0usize, height as usize), (1usize, height as usize / 2)] {
707 let base = CVPixelBufferGetBaseAddressOfPlane(&buffer, plane) as *mut u8;
708 let stride = CVPixelBufferGetBytesPerRowOfPlane(&buffer, plane);
709 unsafe { ptr::write_bytes(base, 128, stride * rows) };
710 }
711 unsafe { CVPixelBufferUnlockBaseAddress(&buffer, flags) };
712
713 crate::frame::macos::PixelBuffer::new(buffer, width, height)
714 }
715
716 fn nal_types(annexb: &[u8]) -> Vec<u8> {
719 let mut types = Vec::new();
720 let mut i = 0;
721 while i + 3 < annexb.len() {
722 if annexb[i..i + 3] == [0, 0, 1] {
723 types.push(annexb[i + 3] & 0x1f);
724 i += 3;
725 } else {
726 i += 1;
727 }
728 }
729 types
730 }
731
732 #[cfg(target_os = "windows")]
736 #[test]
737 #[ignore]
738 fn mediafoundation_cpu_rgba() {
739 let config = Config {
740 kind: Kind::Named("mediafoundation".into()),
741 ..Config::new(640, 480, 30)
742 };
743 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
744 assert_eq!(encoder.name(), "mediafoundation");
745
746 let mut frames = Vec::new();
747 for i in 0..30 {
748 if i == 0 {
749 encoder.keyframe();
750 }
751 frames.extend(encoder.encode(&gray_frame(640, 480, i)).unwrap());
752 }
753 frames.extend(encoder.finish().unwrap());
754
755 assert!(!frames.is_empty(), "encoder produced no packets");
756 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
759 assert!(
760 micros.windows(2).all(|w| w[0] < w[1]),
761 "encoded timestamps not strictly increasing: {micros:?}"
762 );
763 assert!(
764 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
765 "encoded timestamp outside the fed set: {micros:?}"
766 );
767
768 let packets = payloads(&frames);
769 let types = nal_types(&packets[0]);
770 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
771 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
772 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
773 }
774
775 #[cfg(all(target_os = "windows", feature = "capture"))]
779 #[tokio::test]
780 #[ignore]
781 async fn mediafoundation_camera_texture() {
782 let mut camera = crate::capture::open(&crate::capture::Config::default())
783 .await
784 .expect("open default camera");
785 let (w, h) = (camera.width(), camera.height());
786
787 let config = Config {
788 kind: Kind::Named("mediafoundation".into()),
789 ..Config::new(w, h, camera.framerate().unwrap_or(30))
790 };
791 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
792
793 let mut frames = Vec::new();
794 let mut textures = 0;
795 for i in 0..30 {
796 let surface = camera.read().await.expect("frame, not end of stream");
797 if matches!(surface, Surface::Texture(_)) {
798 textures += 1;
799 }
800 if i == 0 {
801 encoder.keyframe();
802 }
803 frames.extend(encoder.encode(&Frame::new(surface, at(i))).unwrap());
804 }
805 frames.extend(encoder.finish().unwrap());
806
807 assert!(textures > 0, "capture never produced a GPU texture");
810 assert!(!frames.is_empty(), "encoder produced no packets");
811 let packets = payloads(&frames);
812 let types = nal_types(&packets[0]);
813 assert!(
814 types.contains(&7) && types.contains(&8) && types.contains(&5),
815 "no IDR: {types:?}"
816 );
817 }
818
819 #[test]
823 fn set_bitrate_retunes_software_encoder() {
824 let config = Config {
825 kind: Kind::Software,
826 ..Config::new(320, 240, 30)
827 };
828 let mut encoder = Encoder::new(&config).unwrap();
829
830 let opened = encoder.bitrate();
831 assert_eq!(opened, config.resolved_bitrate());
832
833 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
835
836 let halved = opened / 2;
837 encoder.set_bitrate(halved).unwrap();
838 assert_eq!(encoder.bitrate(), halved);
839
840 let frames = encoder.encode(&gray_frame(320, 240, 1)).unwrap();
842 assert!(!frames.is_empty(), "encoder produced nothing after a retune");
843 let packets = payloads(&frames);
844 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
845 }
846
847 #[test]
851 fn set_bitrate_before_the_first_frame_is_deferred() {
852 let config = Config {
853 kind: Kind::Software,
854 ..Config::new(320, 240, 30)
855 };
856 let mut encoder = Encoder::new(&config).unwrap();
857
858 let halved = encoder.bitrate() / 2;
859 encoder.set_bitrate(halved).expect("a retune before the first frame");
860 assert_eq!(encoder.bitrate(), halved);
861
862 let frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
864 assert!(!frames.is_empty());
865
866 encoder.set_bitrate(halved / 2).unwrap();
868 assert!(encoder.encode(&gray_frame(320, 240, 1)).is_ok());
869 }
870
871 #[test]
874 fn set_bitrate_to_current_is_a_noop() {
875 let config = Config {
876 kind: Kind::Software,
877 ..Config::new(320, 240, 30)
878 };
879 let mut encoder = Encoder::new(&config).unwrap();
880
881 let opened = encoder.bitrate();
882 encoder.set_bitrate(opened).unwrap();
883 assert_eq!(encoder.bitrate(), opened);
884 }
885
886 #[test]
887 fn default_bitrate_scales_with_resolution() {
888 let small = Config::new(320, 240, 30).resolved_bitrate();
889 let large = Config::new(1920, 1080, 30).resolved_bitrate();
890 assert!(large > small);
891 assert!(small > 0);
892 }
893
894 struct Delayed {
899 pending: Option<Encoded>,
900 }
901
902 impl Backend for Delayed {
903 fn encode(&mut self, frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
904 let payload = bytes::Bytes::from(frame.timestamp.as_micros().to_string());
905 let previous = self.pending.replace(Encoded::new(payload, frame.timestamp));
906 Ok(previous.into_iter().collect())
907 }
908
909 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
910 Ok(self.pending.take().into_iter().collect())
911 }
912
913 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
914 self.flush()
915 }
916
917 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
918 Ok(())
919 }
920
921 fn name(&self) -> &str {
922 "delayed"
923 }
924 }
925
926 fn encoder_with(backend: Box<dyn Backend>, config: &Config) -> Encoder {
929 Encoder {
930 backend,
931 codec: config.codec,
932 size: config.size(),
933 bitrate: config.resolved_bitrate(),
934 color: config.resolved_color(),
935 pending_keyframe: false,
936 }
937 }
938
939 struct Recorder(std::sync::Arc<std::sync::Mutex<Vec<bool>>>);
942
943 impl Backend for Recorder {
944 fn encode(&mut self, _frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error> {
945 self.0.lock().unwrap().push(keyframe);
946 Ok(Vec::new())
947 }
948
949 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
950 Ok(Vec::new())
951 }
952
953 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
954 Ok(Vec::new())
955 }
956
957 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
958 Ok(())
959 }
960
961 fn name(&self) -> &str {
962 "recorder"
963 }
964 }
965
966 #[test]
971 fn a_keyframe_request_waits_for_the_next_frame_then_clears() {
972 let log = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
973 let config = Config::new(320, 240, 30);
974 let mut encoder = encoder_with(Box::new(Recorder(log.clone())), &config);
975
976 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
978
979 encoder.keyframe();
981 encoder.keyframe();
982 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
983
984 encoder.encode(&gray_frame(320, 240, 2)).unwrap();
986
987 assert_eq!(*log.lock().unwrap(), vec![false, true, false]);
988 }
989
990 #[test]
995 fn a_mid_stream_keyframe_request_emits_an_idr() {
996 let config = Config {
997 kind: Kind::Software,
998 ..Config::new(320, 240, 30)
999 };
1000 let mut encoder = Encoder::new(&Config { gop: 1000, ..config }).unwrap();
1003
1004 let mut per_frame = Vec::new();
1005 for i in 0..6 {
1006 if i == 3 {
1008 encoder.keyframe();
1009 }
1010 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1011 let joined: Vec<u8> = encoded.iter().flat_map(|f| f.payload.iter()).copied().collect();
1012 per_frame.push(nal_types(&joined));
1013 }
1014
1015 let asked = &per_frame[3];
1018 assert!(asked.contains(&5), "the requested frame is not an IDR: {asked:?}");
1019 assert!(asked.contains(&7), "no SPS with the requested IDR: {asked:?}");
1020 assert!(asked.contains(&8), "no PPS with the requested IDR: {asked:?}");
1021
1022 for i in [1, 2, 4, 5] {
1026 assert!(
1027 !per_frame[i].contains(&5),
1028 "frame {i} was keyed without being asked: {:?}",
1029 per_frame[i]
1030 );
1031 }
1032 }
1033
1034 struct Failing;
1036
1037 impl Backend for Failing {
1038 fn encode(&mut self, _frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
1039 Err(Error::Codec(anyhow::anyhow!("no")))
1040 }
1041
1042 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
1043 Ok(Vec::new())
1044 }
1045
1046 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
1047 Ok(Vec::new())
1048 }
1049
1050 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
1051 Ok(())
1052 }
1053
1054 fn name(&self) -> &str {
1055 "failing"
1056 }
1057 }
1058
1059 #[test]
1064 fn a_failed_encode_keeps_the_keyframe_request() {
1065 let config = Config::new(320, 240, 30);
1066
1067 let mut encoder = encoder_with(Box::new(Failing), &config);
1068 encoder.keyframe();
1069 assert!(encoder.encode(&gray_frame(320, 240, 0)).is_err());
1070 assert!(encoder.pending_keyframe, "the backend error swallowed the request");
1071
1072 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1074 encoder.keyframe();
1075 assert!(encoder.encode(&gray_frame(640, 480, 0)).is_err());
1076 assert!(encoder.pending_keyframe, "the size check swallowed the request");
1077
1078 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
1080 assert!(!encoder.pending_keyframe);
1081 }
1082
1083 #[test]
1089 fn a_buffering_backend_keeps_each_frames_timestamp() {
1090 let config = Config::new(320, 240, 30);
1091 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1092
1093 for i in 0..5 {
1097 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1098 if i == 0 {
1099 assert!(encoded.is_empty(), "the first frame is still buffered");
1100 continue;
1101 }
1102 assert_eq!(encoded.len(), 1);
1103 assert_eq!(encoded[0].timestamp, at(i - 1));
1104 assert_eq!(&encoded[0].payload[..], at(i - 1).as_micros().to_string().as_bytes());
1107 }
1108
1109 let tail = encoder.finish().unwrap();
1112 assert_eq!(tail.len(), 1);
1113 assert_eq!(tail[0].timestamp, at(4));
1114 assert!(
1115 encoder_with(Box::new(Delayed { pending: None }), &config)
1116 .finish()
1117 .unwrap()
1118 .is_empty()
1119 );
1120 }
1121
1122 #[test]
1130 fn a_flush_empties_a_pipelined_backend_and_leaves_it_running() {
1131 let config = Config::new(320, 240, 30);
1132 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1133
1134 let mut group = Vec::new();
1135 for i in 0..3 {
1136 group.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
1137 }
1138 group.extend(encoder.flush().unwrap());
1139
1140 let times: Vec<_> = group.iter().map(|packet| packet.timestamp).collect();
1142 assert_eq!(times, vec![at(0), at(1), at(2)], "the group lost or reordered frames");
1143
1144 let mut next = encoder.encode(&gray_frame(320, 240, 3)).unwrap();
1147 assert!(next.is_empty(), "frame 3 is buffered, so nothing comes back yet");
1148 next.extend(encoder.finish().unwrap());
1149 let times: Vec<_> = next.iter().map(|packet| packet.timestamp).collect();
1150 assert_eq!(times, vec![at(3)], "the flush left something behind");
1151 }
1152
1153 #[cfg(target_os = "macos")]
1158 #[test]
1159 fn videotoolbox_sps_declares_the_color_space() {
1160 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1161
1162 for (size, described) in [
1163 (Size::new(640, 480), BT601_DESCRIBED),
1164 (Size::new(1920, 1080), BT709_DESCRIBED),
1165 ] {
1166 let config = Config {
1167 kind: Kind::Named("videotoolbox".into()),
1168 ..Config::new(size.width, size.height, 30)
1169 };
1170 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
1171
1172 let rgba = [255u8, 0, 0, 255].repeat(size.pixels() as usize);
1173 let surface = crate::frame::Surface::rgba(&rgba, size).unwrap();
1174 encoder.keyframe();
1175 let frames = encoder
1176 .encode(&Frame::new(surface, moq_net::Timestamp::from_micros(0).unwrap()))
1177 .unwrap();
1178
1179 let keyframe = frames.first().expect("a keyframe");
1180 assert_eq!(declared_color(&keyframe.payload), Some(described), "{size} SPS");
1181 }
1182 }
1183
1184 #[test]
1189 fn config_color_pins_the_space_a_resize_carried() {
1190 use crate::Color;
1191
1192 let big = Size::new(1280, 720);
1193 let small = Size::new(640, 480);
1194
1195 let rgba = vec![0x80u8; big.pixels() as usize * 4];
1197 let frame = Frame::new(
1198 crate::frame::Surface::rgba(&rgba, big).unwrap(),
1199 moq_net::Timestamp::from_micros(0).unwrap(),
1200 );
1201 let scaled = frame.resize(small).unwrap();
1202 assert_eq!(
1203 scaled.surface.color(),
1204 Some(Color::Bt709Limited),
1205 "resize keeps the space"
1206 );
1207
1208 let config = Config {
1212 kind: Kind::Software,
1213 ..Config::new(small.width, small.height, 30)
1214 };
1215 let mut encoder = Encoder::new(&config).unwrap();
1216 encoder.keyframe();
1217 let frames = encoder.encode(&scaled).expect("a mismatch warns rather than fails");
1218 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1219 assert_eq!(
1220 declared_color(&frames.first().expect("a keyframe").payload),
1221 Some(BT601_DESCRIBED),
1222 "the inferred label is the wrong one, which is the case Config::color covers"
1223 );
1224
1225 let config = Config {
1227 kind: Kind::Software,
1228 color: Some(Color::Bt709Limited),
1229 ..Config::new(small.width, small.height, 30)
1230 };
1231 let mut encoder = Encoder::new(&config).unwrap();
1232 encoder.keyframe();
1233 let frames = encoder.encode(&scaled).expect("a declared space encodes");
1234
1235 let keyframe = frames.first().expect("a keyframe");
1236 assert_eq!(declared_color(&keyframe.payload), Some(BT709_DESCRIBED));
1237 }
1238}