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 let err = Encoder::new(&config).err().expect("an unknown name cannot open");
521 assert!(
522 matches!(&err, Error::UnknownEncoder { name, .. } if name == "definitely_not_a_codec"),
523 "unexpected error: {err:?}",
524 );
525 }
526
527 #[cfg(target_os = "macos")]
531 #[test]
532 fn videotoolbox_emits_annexb_keyframe() {
533 let config = Config {
534 kind: Kind::Named("videotoolbox".into()),
535 ..Config::new(320, 240, 30)
536 };
537 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
538 assert_eq!(encoder.name(), "videotoolbox");
539
540 let mut frames = Vec::new();
541 for i in 0..10 {
542 if i == 0 {
543 encoder.keyframe();
544 }
545 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
546 }
547 frames.extend(encoder.finish().unwrap());
548
549 assert!(!frames.is_empty(), "encoder produced no packets");
550 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
553 assert!(
554 micros.windows(2).all(|w| w[0] < w[1]),
555 "encoded timestamps not strictly increasing: {micros:?}"
556 );
557
558 let packets = payloads(&frames);
559 let first = &packets[0];
560 assert!(
561 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
562 "first packet is not Annex-B"
563 );
564
565 let types = nal_types(first);
568 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
569 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
570 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
571 }
572
573 #[cfg(target_os = "macos")]
577 #[test]
578 fn videotoolbox_emits_annexb_keyframe_h265() {
579 let config = Config {
580 codec: Codec::H265,
581 kind: Kind::Named("videotoolbox".into()),
582 ..Config::new(320, 240, 30)
583 };
584 let mut encoder = Encoder::new(&config).expect("videotoolbox HEVC is available on macOS");
585 assert_eq!(encoder.name(), "videotoolbox");
586 assert_eq!(encoder.codec(), Codec::H265);
587
588 let mut frames = Vec::new();
589 for i in 0..10 {
590 if i == 0 {
591 encoder.keyframe();
592 }
593 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
594 }
595 frames.extend(encoder.finish().unwrap());
596
597 assert!(!frames.is_empty(), "encoder produced no packets");
598 let packets = payloads(&frames);
599 let first = &packets[0];
600 assert!(
601 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
602 "first packet is not Annex-B"
603 );
604
605 let types = hevc_nal_types(first);
608 assert!(types.contains(&32), "no VPS in first packet: {types:?}");
609 assert!(types.contains(&33), "no SPS in first packet: {types:?}");
610 assert!(types.contains(&34), "no PPS in first packet: {types:?}");
611 assert!(
612 types.iter().any(|t| (16..=23).contains(t)),
613 "first packet is not an IRAP: {types:?}"
614 );
615 }
616
617 #[cfg(target_os = "macos")]
619 fn hevc_nal_types(annexb: &[u8]) -> Vec<u8> {
620 let mut types = Vec::new();
621 let mut i = 0;
622 while i + 3 < annexb.len() {
623 if annexb[i..i + 3] == [0, 0, 1] {
624 types.push((annexb[i + 3] >> 1) & 0x3f);
625 i += 3;
626 } else {
627 i += 1;
628 }
629 }
630 types
631 }
632
633 #[cfg(target_os = "macos")]
636 #[test]
637 fn videotoolbox_encodes_surface_zero_copy() {
638 let config = Config {
639 kind: Kind::Named("videotoolbox".into()),
640 ..Config::new(320, 240, 30)
641 };
642 let mut encoder = Encoder::new(&config).unwrap();
643
644 let mut frames = Vec::new();
645 for i in 0..10 {
646 if i == 0 {
647 encoder.keyframe();
648 }
649 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(i));
650 frames.extend(encoder.encode(&frame).unwrap());
651 }
652 frames.extend(encoder.finish().unwrap());
653
654 assert!(!frames.is_empty());
655 let packets = payloads(&frames);
656 let types = nal_types(&packets[0]);
657 assert!(
658 types.contains(&7) && types.contains(&8) && types.contains(&5),
659 "no IDR: {types:?}"
660 );
661 }
662
663 #[cfg(target_os = "macos")]
666 #[test]
667 fn openh264_downloads_surface() {
668 let config = Config {
669 kind: Kind::Software,
670 ..Config::new(320, 240, 30)
671 };
672 let mut encoder = Encoder::new(&config).unwrap();
673
674 encoder.keyframe();
675 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(0));
676 let mut frames = encoder.encode(&frame).unwrap();
677 frames.extend(encoder.finish().unwrap());
678
679 assert!(!frames.is_empty());
680 let packets = payloads(&frames);
681 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
682 }
683
684 #[cfg(target_os = "macos")]
687 fn nv12_surface(width: u32, height: u32) -> crate::frame::macos::PixelBuffer {
688 use std::ptr::{self, NonNull};
689
690 use objc2_core_foundation::CFRetained;
691 use objc2_core_video::{
692 CVPixelBuffer, CVPixelBufferCreate, CVPixelBufferGetBaseAddressOfPlane, CVPixelBufferGetBytesPerRowOfPlane,
693 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
694 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
695 };
696
697 let mut raw: *mut CVPixelBuffer = ptr::null_mut();
698 let status = unsafe {
699 CVPixelBufferCreate(
700 None,
701 width as usize,
702 height as usize,
703 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
704 None,
705 NonNull::new(&mut raw).unwrap(),
706 )
707 };
708 assert_eq!(status, 0, "CVPixelBufferCreate failed");
709 let buffer = unsafe { CFRetained::from_raw(NonNull::new(raw).unwrap()) };
710
711 let flags = CVPixelBufferLockFlags(0);
712 assert_eq!(unsafe { CVPixelBufferLockBaseAddress(&buffer, flags) }, 0);
713 for (plane, rows) in [(0usize, height as usize), (1usize, height as usize / 2)] {
714 let base = CVPixelBufferGetBaseAddressOfPlane(&buffer, plane) as *mut u8;
715 let stride = CVPixelBufferGetBytesPerRowOfPlane(&buffer, plane);
716 unsafe { ptr::write_bytes(base, 128, stride * rows) };
717 }
718 unsafe { CVPixelBufferUnlockBaseAddress(&buffer, flags) };
719
720 crate::frame::macos::PixelBuffer::new(buffer, width, height)
721 }
722
723 fn nal_types(annexb: &[u8]) -> Vec<u8> {
726 let mut types = Vec::new();
727 let mut i = 0;
728 while i + 3 < annexb.len() {
729 if annexb[i..i + 3] == [0, 0, 1] {
730 types.push(annexb[i + 3] & 0x1f);
731 i += 3;
732 } else {
733 i += 1;
734 }
735 }
736 types
737 }
738
739 #[cfg(target_os = "windows")]
743 #[test]
744 #[ignore]
745 fn mediafoundation_cpu_rgba() {
746 let config = Config {
747 kind: Kind::Named("mediafoundation".into()),
748 ..Config::new(640, 480, 30)
749 };
750 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
751 assert_eq!(encoder.name(), "mediafoundation");
752
753 let mut frames = Vec::new();
754 for i in 0..30 {
755 if i == 0 {
756 encoder.keyframe();
757 }
758 frames.extend(encoder.encode(&gray_frame(640, 480, i)).unwrap());
759 }
760 frames.extend(encoder.finish().unwrap());
761
762 assert!(!frames.is_empty(), "encoder produced no packets");
763 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
766 assert!(
767 micros.windows(2).all(|w| w[0] < w[1]),
768 "encoded timestamps not strictly increasing: {micros:?}"
769 );
770 assert!(
771 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
772 "encoded timestamp outside the fed set: {micros:?}"
773 );
774
775 let packets = payloads(&frames);
776 let types = nal_types(&packets[0]);
777 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
778 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
779 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
780 }
781
782 #[cfg(all(target_os = "windows", feature = "capture"))]
786 #[tokio::test]
787 #[ignore]
788 async fn mediafoundation_camera_texture() {
789 let mut camera = crate::capture::open(&crate::capture::Config::default())
790 .await
791 .expect("open default camera");
792 let (w, h) = (camera.width(), camera.height());
793
794 let config = Config {
795 kind: Kind::Named("mediafoundation".into()),
796 ..Config::new(w, h, camera.framerate().unwrap_or(30))
797 };
798 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
799
800 let mut frames = Vec::new();
801 let mut textures = 0;
802 for i in 0..30 {
803 let surface = camera.read().await.expect("read camera frame");
804 if matches!(surface, Some(Surface::Texture(_))) {
805 textures += 1;
806 }
807 if i == 0 {
808 encoder.keyframe();
809 }
810 let surface = surface.expect("frame, not end of stream");
811 frames.extend(encoder.encode(&Frame::new(surface, at(i))).unwrap());
812 }
813 frames.extend(encoder.finish().unwrap());
814
815 assert!(textures > 0, "capture never produced a GPU texture");
818 assert!(!frames.is_empty(), "encoder produced no packets");
819 let packets = payloads(&frames);
820 let types = nal_types(&packets[0]);
821 assert!(
822 types.contains(&7) && types.contains(&8) && types.contains(&5),
823 "no IDR: {types:?}"
824 );
825 }
826
827 #[test]
831 fn set_bitrate_retunes_software_encoder() {
832 let config = Config {
833 kind: Kind::Software,
834 ..Config::new(320, 240, 30)
835 };
836 let mut encoder = Encoder::new(&config).unwrap();
837
838 let opened = encoder.bitrate();
839 assert_eq!(opened, config.resolved_bitrate());
840
841 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
843
844 let halved = opened / 2;
845 encoder.set_bitrate(halved).unwrap();
846 assert_eq!(encoder.bitrate(), halved);
847
848 let frames = encoder.encode(&gray_frame(320, 240, 1)).unwrap();
850 assert!(!frames.is_empty(), "encoder produced nothing after a retune");
851 let packets = payloads(&frames);
852 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
853 }
854
855 #[test]
859 fn set_bitrate_before_the_first_frame_is_deferred() {
860 let config = Config {
861 kind: Kind::Software,
862 ..Config::new(320, 240, 30)
863 };
864 let mut encoder = Encoder::new(&config).unwrap();
865
866 let halved = encoder.bitrate() / 2;
867 encoder.set_bitrate(halved).expect("a retune before the first frame");
868 assert_eq!(encoder.bitrate(), halved);
869
870 let frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
872 assert!(!frames.is_empty());
873
874 encoder.set_bitrate(halved / 2).unwrap();
876 assert!(encoder.encode(&gray_frame(320, 240, 1)).is_ok());
877 }
878
879 #[test]
882 fn set_bitrate_to_current_is_a_noop() {
883 let config = Config {
884 kind: Kind::Software,
885 ..Config::new(320, 240, 30)
886 };
887 let mut encoder = Encoder::new(&config).unwrap();
888
889 let opened = encoder.bitrate();
890 encoder.set_bitrate(opened).unwrap();
891 assert_eq!(encoder.bitrate(), opened);
892 }
893
894 #[test]
895 fn default_bitrate_scales_with_resolution() {
896 let small = Config::new(320, 240, 30).resolved_bitrate();
897 let large = Config::new(1920, 1080, 30).resolved_bitrate();
898 assert!(large > small);
899 assert!(small > 0);
900 }
901
902 struct Delayed {
907 pending: Option<Encoded>,
908 }
909
910 impl Backend for Delayed {
911 fn encode(&mut self, frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
912 let payload = bytes::Bytes::from(frame.timestamp.as_micros().to_string());
913 let previous = self.pending.replace(Encoded::new(payload, frame.timestamp));
914 Ok(previous.into_iter().collect())
915 }
916
917 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
918 Ok(self.pending.take().into_iter().collect())
919 }
920
921 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
922 self.flush()
923 }
924
925 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
926 Ok(())
927 }
928
929 fn name(&self) -> &str {
930 "delayed"
931 }
932 }
933
934 fn encoder_with(backend: Box<dyn Backend>, config: &Config) -> Encoder {
937 Encoder {
938 backend,
939 codec: config.codec,
940 size: config.size(),
941 bitrate: config.resolved_bitrate(),
942 color: config.resolved_color(),
943 pending_keyframe: false,
944 }
945 }
946
947 struct Recorder(std::sync::Arc<std::sync::Mutex<Vec<bool>>>);
950
951 impl Backend for Recorder {
952 fn encode(&mut self, _frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error> {
953 self.0.lock().unwrap().push(keyframe);
954 Ok(Vec::new())
955 }
956
957 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
958 Ok(Vec::new())
959 }
960
961 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
962 Ok(Vec::new())
963 }
964
965 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
966 Ok(())
967 }
968
969 fn name(&self) -> &str {
970 "recorder"
971 }
972 }
973
974 #[test]
979 fn a_keyframe_request_waits_for_the_next_frame_then_clears() {
980 let log = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
981 let config = Config::new(320, 240, 30);
982 let mut encoder = encoder_with(Box::new(Recorder(log.clone())), &config);
983
984 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
986
987 encoder.keyframe();
989 encoder.keyframe();
990 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
991
992 encoder.encode(&gray_frame(320, 240, 2)).unwrap();
994
995 assert_eq!(*log.lock().unwrap(), vec![false, true, false]);
996 }
997
998 #[test]
1003 fn a_mid_stream_keyframe_request_emits_an_idr() {
1004 let config = Config {
1005 kind: Kind::Software,
1006 ..Config::new(320, 240, 30)
1007 };
1008 let mut encoder = Encoder::new(&Config { gop: 1000, ..config }).unwrap();
1011
1012 let mut per_frame = Vec::new();
1013 for i in 0..6 {
1014 if i == 3 {
1016 encoder.keyframe();
1017 }
1018 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1019 let joined: Vec<u8> = encoded.iter().flat_map(|f| f.payload.iter()).copied().collect();
1020 per_frame.push(nal_types(&joined));
1021 }
1022
1023 let asked = &per_frame[3];
1026 assert!(asked.contains(&5), "the requested frame is not an IDR: {asked:?}");
1027 assert!(asked.contains(&7), "no SPS with the requested IDR: {asked:?}");
1028 assert!(asked.contains(&8), "no PPS with the requested IDR: {asked:?}");
1029
1030 for i in [1, 2, 4, 5] {
1034 assert!(
1035 !per_frame[i].contains(&5),
1036 "frame {i} was keyed without being asked: {:?}",
1037 per_frame[i]
1038 );
1039 }
1040 }
1041
1042 struct Failing;
1044
1045 impl Backend for Failing {
1046 fn encode(&mut self, _frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
1047 Err(Error::Codec(anyhow::anyhow!("no")))
1048 }
1049
1050 fn flush(&mut self) -> Result<Vec<Encoded>, Error> {
1051 Ok(Vec::new())
1052 }
1053
1054 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
1055 Ok(Vec::new())
1056 }
1057
1058 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
1059 Ok(())
1060 }
1061
1062 fn name(&self) -> &str {
1063 "failing"
1064 }
1065 }
1066
1067 #[test]
1072 fn a_failed_encode_keeps_the_keyframe_request() {
1073 let config = Config::new(320, 240, 30);
1074
1075 let mut encoder = encoder_with(Box::new(Failing), &config);
1076 encoder.keyframe();
1077 assert!(encoder.encode(&gray_frame(320, 240, 0)).is_err());
1078 assert!(encoder.pending_keyframe, "the backend error swallowed the request");
1079
1080 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1082 encoder.keyframe();
1083 assert!(encoder.encode(&gray_frame(640, 480, 0)).is_err());
1084 assert!(encoder.pending_keyframe, "the size check swallowed the request");
1085
1086 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
1088 assert!(!encoder.pending_keyframe);
1089 }
1090
1091 #[test]
1097 fn a_buffering_backend_keeps_each_frames_timestamp() {
1098 let config = Config::new(320, 240, 30);
1099 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1100
1101 for i in 0..5 {
1105 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1106 if i == 0 {
1107 assert!(encoded.is_empty(), "the first frame is still buffered");
1108 continue;
1109 }
1110 assert_eq!(encoded.len(), 1);
1111 assert_eq!(encoded[0].timestamp, at(i - 1));
1112 assert_eq!(&encoded[0].payload[..], at(i - 1).as_micros().to_string().as_bytes());
1115 }
1116
1117 let tail = encoder.finish().unwrap();
1120 assert_eq!(tail.len(), 1);
1121 assert_eq!(tail[0].timestamp, at(4));
1122 assert!(
1123 encoder_with(Box::new(Delayed { pending: None }), &config)
1124 .finish()
1125 .unwrap()
1126 .is_empty()
1127 );
1128 }
1129
1130 #[test]
1138 fn a_flush_empties_a_pipelined_backend_and_leaves_it_running() {
1139 let config = Config::new(320, 240, 30);
1140 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
1141
1142 let mut group = Vec::new();
1143 for i in 0..3 {
1144 group.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
1145 }
1146 group.extend(encoder.flush().unwrap());
1147
1148 let times: Vec<_> = group.iter().map(|packet| packet.timestamp).collect();
1150 assert_eq!(times, vec![at(0), at(1), at(2)], "the group lost or reordered frames");
1151
1152 let mut next = encoder.encode(&gray_frame(320, 240, 3)).unwrap();
1155 assert!(next.is_empty(), "frame 3 is buffered, so nothing comes back yet");
1156 next.extend(encoder.finish().unwrap());
1157 let times: Vec<_> = next.iter().map(|packet| packet.timestamp).collect();
1158 assert_eq!(times, vec![at(3)], "the flush left something behind");
1159 }
1160
1161 #[cfg(target_os = "macos")]
1166 #[test]
1167 fn videotoolbox_sps_declares_the_color_space() {
1168 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1169
1170 for (size, described) in [
1171 (Size::new(640, 480), BT601_DESCRIBED),
1172 (Size::new(1920, 1080), BT709_DESCRIBED),
1173 ] {
1174 let config = Config {
1175 kind: Kind::Named("videotoolbox".into()),
1176 ..Config::new(size.width, size.height, 30)
1177 };
1178 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
1179
1180 let rgba = [255u8, 0, 0, 255].repeat(size.pixels() as usize);
1181 let surface = crate::frame::Surface::rgba(&rgba, size).unwrap();
1182 encoder.keyframe();
1183 let frames = encoder
1184 .encode(&Frame::new(surface, moq_net::Timestamp::from_micros(0).unwrap()))
1185 .unwrap();
1186
1187 let keyframe = frames.first().expect("a keyframe");
1188 assert_eq!(declared_color(&keyframe.payload), Some(described), "{size} SPS");
1189 }
1190 }
1191
1192 #[test]
1197 fn config_color_pins_the_space_a_resize_carried() {
1198 use crate::Color;
1199
1200 let big = Size::new(1280, 720);
1201 let small = Size::new(640, 480);
1202
1203 let rgba = vec![0x80u8; big.pixels() as usize * 4];
1205 let frame = Frame::new(
1206 crate::frame::Surface::rgba(&rgba, big).unwrap(),
1207 moq_net::Timestamp::from_micros(0).unwrap(),
1208 );
1209 let scaled = frame.resize(small).unwrap();
1210 assert_eq!(
1211 scaled.surface.color(),
1212 Some(Color::Bt709Limited),
1213 "resize keeps the space"
1214 );
1215
1216 let config = Config {
1220 kind: Kind::Software,
1221 ..Config::new(small.width, small.height, 30)
1222 };
1223 let mut encoder = Encoder::new(&config).unwrap();
1224 encoder.keyframe();
1225 let frames = encoder.encode(&scaled).expect("a mismatch warns rather than fails");
1226 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1227 assert_eq!(
1228 declared_color(&frames.first().expect("a keyframe").payload),
1229 Some(BT601_DESCRIBED),
1230 "the inferred label is the wrong one, which is the case Config::color covers"
1231 );
1232
1233 let config = Config {
1235 kind: Kind::Software,
1236 color: Some(Color::Bt709Limited),
1237 ..Config::new(small.width, small.height, 30)
1238 };
1239 let mut encoder = Encoder::new(&config).unwrap();
1240 encoder.keyframe();
1241 let frames = encoder.encode(&scaled).expect("a declared space encodes");
1242
1243 let keyframe = frames.first().expect("a keyframe");
1244 assert_eq!(declared_color(&keyframe.payload), Some(BT709_DESCRIBED));
1245 }
1246}