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 finish(mut self) -> Result<Vec<Encoded>, Error> {
282 self.backend.finish()
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 use crate::{I420, Surface};
291
292 fn gray_rgba(width: u32, height: u32) -> Vec<u8> {
294 vec![0x80u8; width as usize * height as usize * 4]
295 }
296
297 fn gray_frame(width: u32, height: u32, index: u64) -> Frame {
300 let surface = Surface::rgba(&gray_rgba(width, height), Size::new(width, height)).unwrap();
301 Frame::new(surface, at(index))
302 }
303
304 fn at(index: u64) -> moq_net::Timestamp {
306 moq_net::Timestamp::from_micros(index * 33_333).unwrap()
307 }
308
309 fn payloads(frames: &[Encoded]) -> Vec<bytes::Bytes> {
311 frames.iter().map(|f| f.payload.clone()).collect()
312 }
313
314 #[test]
315 fn software_encoder_emits_annexb() {
316 let config = Config {
317 kind: Kind::Software,
318 ..Config::new(320, 240, 30)
319 };
320 let mut encoder = Encoder::new(&config).expect("openh264 is vendored, always available");
321 assert_eq!(encoder.name(), "openh264");
322
323 let mut frames = Vec::new();
324 for i in 0..30 {
325 if i == 0 {
326 encoder.keyframe();
327 }
328 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
329 }
330 frames.extend(encoder.finish().unwrap());
331
332 assert!(!frames.is_empty(), "encoder produced no packets");
333
334 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
337 assert!(
338 micros.windows(2).all(|w| w[0] < w[1]),
339 "encoded timestamps not strictly increasing: {micros:?}"
340 );
341 assert!(
342 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
343 "encoded timestamp outside the fed set: {micros:?}"
344 );
345
346 let packets = payloads(&frames);
349 let first = &packets[0];
350 let has_start_code = first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]);
351 assert!(
352 has_start_code,
353 "first packet is not Annex-B: {:02x?}",
354 &first[..first.len().min(8)]
355 );
356 }
357
358 #[test]
360 fn encode_rgba_surface_emits_annexb() {
361 let config = Config {
362 kind: Kind::Software,
363 ..Config::new(320, 240, 30)
364 };
365 let mut encoder = Encoder::new(&config).unwrap();
366
367 let mut frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
368 frames.extend(encoder.finish().unwrap());
369 assert!(!frames.is_empty());
370 let packets = payloads(&frames);
371 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
372 }
373
374 #[test]
376 fn encode_i420_surface_emits_annexb() {
377 let config = Config {
378 kind: Kind::Software,
379 ..Config::new(320, 240, 30)
380 };
381 let mut encoder = Encoder::new(&config).unwrap();
382
383 let i420 = I420::new(320, 240, vec![0x80u8; I420::len(320, 240)]).unwrap();
385 let frame = Frame::new(Surface::I420(i420), at(0));
386 let mut frames = encoder.encode(&frame).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]
396 fn encode_rejects_dimension_mismatch() {
397 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
398 return;
399 };
400 assert!(matches!(encoder.encode(&gray_frame(640, 480, 0)), Err(Error::Codec(_))));
401 }
402
403 #[test]
407 fn encode_rejects_transposed_frame() {
408 let Ok(mut encoder) = Encoder::new(&Config::new(320, 240, 30)) else {
409 return;
410 };
411
412 let transposed = gray_frame(240, 320, 0);
413 assert_eq!(
414 gray_rgba(240, 320).len(),
415 gray_rgba(320, 240).len(),
416 "the byte counts must collide"
417 );
418 assert!(matches!(encoder.encode(&transposed), Err(Error::Codec(_))));
419 }
420
421 #[test]
422 fn new_rejects_zero_framerate() {
423 let config = Config::new(320, 240, 0);
426 assert!(matches!(Encoder::new(&config), Err(Error::InvalidFramerate(0))));
427 }
428
429 #[test]
430 fn unknown_named_encoder_errors() {
431 let config = Config {
432 kind: Kind::Named("definitely_not_a_codec".into()),
433 ..Config::new(320, 240, 30)
434 };
435 assert!(matches!(Encoder::new(&config), Err(Error::NoEncoder(_))));
436 }
437
438 #[cfg(target_os = "macos")]
442 #[test]
443 fn videotoolbox_emits_annexb_keyframe() {
444 let config = Config {
445 kind: Kind::Named("videotoolbox".into()),
446 ..Config::new(320, 240, 30)
447 };
448 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
449 assert_eq!(encoder.name(), "videotoolbox");
450
451 let mut frames = Vec::new();
452 for i in 0..10 {
453 if i == 0 {
454 encoder.keyframe();
455 }
456 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
457 }
458 frames.extend(encoder.finish().unwrap());
459
460 assert!(!frames.is_empty(), "encoder produced no packets");
461 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
464 assert!(
465 micros.windows(2).all(|w| w[0] < w[1]),
466 "encoded timestamps not strictly increasing: {micros:?}"
467 );
468
469 let packets = payloads(&frames);
470 let first = &packets[0];
471 assert!(
472 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
473 "first packet is not Annex-B"
474 );
475
476 let types = nal_types(first);
479 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
480 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
481 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
482 }
483
484 #[cfg(target_os = "macos")]
488 #[test]
489 fn videotoolbox_emits_annexb_keyframe_h265() {
490 let config = Config {
491 codec: Codec::H265,
492 kind: Kind::Named("videotoolbox".into()),
493 ..Config::new(320, 240, 30)
494 };
495 let mut encoder = Encoder::new(&config).expect("videotoolbox HEVC is available on macOS");
496 assert_eq!(encoder.name(), "videotoolbox");
497 assert_eq!(encoder.codec(), Codec::H265);
498
499 let mut frames = Vec::new();
500 for i in 0..10 {
501 if i == 0 {
502 encoder.keyframe();
503 }
504 frames.extend(encoder.encode(&gray_frame(320, 240, i)).unwrap());
505 }
506 frames.extend(encoder.finish().unwrap());
507
508 assert!(!frames.is_empty(), "encoder produced no packets");
509 let packets = payloads(&frames);
510 let first = &packets[0];
511 assert!(
512 first.starts_with(&[0, 0, 0, 1]) || first.starts_with(&[0, 0, 1]),
513 "first packet is not Annex-B"
514 );
515
516 let types = hevc_nal_types(first);
519 assert!(types.contains(&32), "no VPS in first packet: {types:?}");
520 assert!(types.contains(&33), "no SPS in first packet: {types:?}");
521 assert!(types.contains(&34), "no PPS in first packet: {types:?}");
522 assert!(
523 types.iter().any(|t| (16..=23).contains(t)),
524 "first packet is not an IRAP: {types:?}"
525 );
526 }
527
528 #[cfg(target_os = "macos")]
530 fn hevc_nal_types(annexb: &[u8]) -> Vec<u8> {
531 let mut types = Vec::new();
532 let mut i = 0;
533 while i + 3 < annexb.len() {
534 if annexb[i..i + 3] == [0, 0, 1] {
535 types.push((annexb[i + 3] >> 1) & 0x3f);
536 i += 3;
537 } else {
538 i += 1;
539 }
540 }
541 types
542 }
543
544 #[cfg(target_os = "macos")]
547 #[test]
548 fn videotoolbox_encodes_surface_zero_copy() {
549 let config = Config {
550 kind: Kind::Named("videotoolbox".into()),
551 ..Config::new(320, 240, 30)
552 };
553 let mut encoder = Encoder::new(&config).unwrap();
554
555 let mut frames = Vec::new();
556 for i in 0..10 {
557 if i == 0 {
558 encoder.keyframe();
559 }
560 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(i));
561 frames.extend(encoder.encode(&frame).unwrap());
562 }
563 frames.extend(encoder.finish().unwrap());
564
565 assert!(!frames.is_empty());
566 let packets = payloads(&frames);
567 let types = nal_types(&packets[0]);
568 assert!(
569 types.contains(&7) && types.contains(&8) && types.contains(&5),
570 "no IDR: {types:?}"
571 );
572 }
573
574 #[cfg(target_os = "macos")]
577 #[test]
578 fn openh264_downloads_surface() {
579 let config = Config {
580 kind: Kind::Software,
581 ..Config::new(320, 240, 30)
582 };
583 let mut encoder = Encoder::new(&config).unwrap();
584
585 encoder.keyframe();
586 let frame = Frame::new(Surface::PixelBuffer(nv12_surface(320, 240)), at(0));
587 let mut frames = encoder.encode(&frame).unwrap();
588 frames.extend(encoder.finish().unwrap());
589
590 assert!(!frames.is_empty());
591 let packets = payloads(&frames);
592 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
593 }
594
595 #[cfg(target_os = "macos")]
598 fn nv12_surface(width: u32, height: u32) -> crate::frame::macos::PixelBuffer {
599 use std::ptr::{self, NonNull};
600
601 use objc2_core_foundation::CFRetained;
602 use objc2_core_video::{
603 CVPixelBuffer, CVPixelBufferCreate, CVPixelBufferGetBaseAddressOfPlane, CVPixelBufferGetBytesPerRowOfPlane,
604 CVPixelBufferLockBaseAddress, CVPixelBufferLockFlags, CVPixelBufferUnlockBaseAddress,
605 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
606 };
607
608 let mut raw: *mut CVPixelBuffer = ptr::null_mut();
609 let status = unsafe {
610 CVPixelBufferCreate(
611 None,
612 width as usize,
613 height as usize,
614 kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
615 None,
616 NonNull::new(&mut raw).unwrap(),
617 )
618 };
619 assert_eq!(status, 0, "CVPixelBufferCreate failed");
620 let buffer = unsafe { CFRetained::from_raw(NonNull::new(raw).unwrap()) };
621
622 let flags = CVPixelBufferLockFlags(0);
623 assert_eq!(unsafe { CVPixelBufferLockBaseAddress(&buffer, flags) }, 0);
624 for (plane, rows) in [(0usize, height as usize), (1usize, height as usize / 2)] {
625 let base = CVPixelBufferGetBaseAddressOfPlane(&buffer, plane) as *mut u8;
626 let stride = CVPixelBufferGetBytesPerRowOfPlane(&buffer, plane);
627 unsafe { ptr::write_bytes(base, 128, stride * rows) };
628 }
629 unsafe { CVPixelBufferUnlockBaseAddress(&buffer, flags) };
630
631 crate::frame::macos::PixelBuffer::new(buffer, width, height)
632 }
633
634 fn nal_types(annexb: &[u8]) -> Vec<u8> {
637 let mut types = Vec::new();
638 let mut i = 0;
639 while i + 3 < annexb.len() {
640 if annexb[i..i + 3] == [0, 0, 1] {
641 types.push(annexb[i + 3] & 0x1f);
642 i += 3;
643 } else {
644 i += 1;
645 }
646 }
647 types
648 }
649
650 #[cfg(target_os = "windows")]
654 #[test]
655 #[ignore]
656 fn mediafoundation_cpu_rgba() {
657 let config = Config {
658 kind: Kind::Named("mediafoundation".into()),
659 ..Config::new(640, 480, 30)
660 };
661 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
662 assert_eq!(encoder.name(), "mediafoundation");
663
664 let mut frames = Vec::new();
665 for i in 0..30 {
666 if i == 0 {
667 encoder.keyframe();
668 }
669 frames.extend(encoder.encode(&gray_frame(640, 480, i)).unwrap());
670 }
671 frames.extend(encoder.finish().unwrap());
672
673 assert!(!frames.is_empty(), "encoder produced no packets");
674 let micros: Vec<u128> = frames.iter().map(|f| f.timestamp.as_micros()).collect();
677 assert!(
678 micros.windows(2).all(|w| w[0] < w[1]),
679 "encoded timestamps not strictly increasing: {micros:?}"
680 );
681 assert!(
682 micros.iter().all(|&t| t % 33_333 == 0 && t < 30 * 33_333),
683 "encoded timestamp outside the fed set: {micros:?}"
684 );
685
686 let packets = payloads(&frames);
687 let types = nal_types(&packets[0]);
688 assert!(types.contains(&7), "no SPS in first packet: {types:?}");
689 assert!(types.contains(&8), "no PPS in first packet: {types:?}");
690 assert!(types.contains(&5), "first packet is not an IDR: {types:?}");
691 }
692
693 #[cfg(target_os = "windows")]
697 #[tokio::test]
698 #[ignore]
699 async fn mediafoundation_camera_texture() {
700 let mut camera = crate::capture::open(&crate::capture::Config::default())
701 .await
702 .expect("open default camera");
703 let (w, h) = (camera.width(), camera.height());
704
705 let config = Config {
706 kind: Kind::Named("mediafoundation".into()),
707 ..Config::new(w, h, camera.framerate().unwrap_or(30))
708 };
709 let mut encoder = Encoder::new(&config).expect("hardware H.264 encoder available");
710
711 let mut frames = Vec::new();
712 let mut textures = 0;
713 for i in 0..30 {
714 let surface = camera.read().await.expect("frame, not end of stream");
715 if matches!(surface, Surface::Texture(_)) {
716 textures += 1;
717 }
718 if i == 0 {
719 encoder.keyframe();
720 }
721 frames.extend(encoder.encode(&Frame::new(surface, at(i))).unwrap());
722 }
723 frames.extend(encoder.finish().unwrap());
724
725 assert!(textures > 0, "capture never produced a GPU texture");
728 assert!(!frames.is_empty(), "encoder produced no packets");
729 let packets = payloads(&frames);
730 let types = nal_types(&packets[0]);
731 assert!(
732 types.contains(&7) && types.contains(&8) && types.contains(&5),
733 "no IDR: {types:?}"
734 );
735 }
736
737 #[test]
741 fn set_bitrate_retunes_software_encoder() {
742 let config = Config {
743 kind: Kind::Software,
744 ..Config::new(320, 240, 30)
745 };
746 let mut encoder = Encoder::new(&config).unwrap();
747
748 let opened = encoder.bitrate();
749 assert_eq!(opened, config.resolved_bitrate());
750
751 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
753
754 let halved = opened / 2;
755 encoder.set_bitrate(halved).unwrap();
756 assert_eq!(encoder.bitrate(), halved);
757
758 let frames = encoder.encode(&gray_frame(320, 240, 1)).unwrap();
760 assert!(!frames.is_empty(), "encoder produced nothing after a retune");
761 let packets = payloads(&frames);
762 assert!(packets[0].starts_with(&[0, 0, 0, 1]) || packets[0].starts_with(&[0, 0, 1]));
763 }
764
765 #[test]
769 fn set_bitrate_before_the_first_frame_is_deferred() {
770 let config = Config {
771 kind: Kind::Software,
772 ..Config::new(320, 240, 30)
773 };
774 let mut encoder = Encoder::new(&config).unwrap();
775
776 let halved = encoder.bitrate() / 2;
777 encoder.set_bitrate(halved).expect("a retune before the first frame");
778 assert_eq!(encoder.bitrate(), halved);
779
780 let frames = encoder.encode(&gray_frame(320, 240, 0)).unwrap();
782 assert!(!frames.is_empty());
783
784 encoder.set_bitrate(halved / 2).unwrap();
786 assert!(encoder.encode(&gray_frame(320, 240, 1)).is_ok());
787 }
788
789 #[test]
792 fn set_bitrate_to_current_is_a_noop() {
793 let config = Config {
794 kind: Kind::Software,
795 ..Config::new(320, 240, 30)
796 };
797 let mut encoder = Encoder::new(&config).unwrap();
798
799 let opened = encoder.bitrate();
800 encoder.set_bitrate(opened).unwrap();
801 assert_eq!(encoder.bitrate(), opened);
802 }
803
804 #[test]
805 fn default_bitrate_scales_with_resolution() {
806 let small = Config::new(320, 240, 30).resolved_bitrate();
807 let large = Config::new(1920, 1080, 30).resolved_bitrate();
808 assert!(large > small);
809 assert!(small > 0);
810 }
811
812 struct Delayed {
817 pending: Option<Encoded>,
818 }
819
820 impl Backend for Delayed {
821 fn encode(&mut self, frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
822 let payload = bytes::Bytes::from(frame.timestamp.as_micros().to_string());
823 let previous = self.pending.replace(Encoded::new(payload, frame.timestamp));
824 Ok(previous.into_iter().collect())
825 }
826
827 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
828 Ok(self.pending.take().into_iter().collect())
829 }
830
831 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
832 Ok(())
833 }
834
835 fn name(&self) -> &str {
836 "delayed"
837 }
838 }
839
840 fn encoder_with(backend: Box<dyn Backend>, config: &Config) -> Encoder {
843 Encoder {
844 backend,
845 codec: config.codec,
846 size: config.size(),
847 bitrate: config.resolved_bitrate(),
848 color: config.resolved_color(),
849 pending_keyframe: false,
850 }
851 }
852
853 struct Recorder(std::sync::Arc<std::sync::Mutex<Vec<bool>>>);
856
857 impl Backend for Recorder {
858 fn encode(&mut self, _frame: &Frame, keyframe: bool) -> Result<Vec<Encoded>, Error> {
859 self.0.lock().unwrap().push(keyframe);
860 Ok(Vec::new())
861 }
862
863 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
864 Ok(Vec::new())
865 }
866
867 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
868 Ok(())
869 }
870
871 fn name(&self) -> &str {
872 "recorder"
873 }
874 }
875
876 #[test]
881 fn a_keyframe_request_waits_for_the_next_frame_then_clears() {
882 let log = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
883 let config = Config::new(320, 240, 30);
884 let mut encoder = encoder_with(Box::new(Recorder(log.clone())), &config);
885
886 encoder.encode(&gray_frame(320, 240, 0)).unwrap();
888
889 encoder.keyframe();
891 encoder.keyframe();
892 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
893
894 encoder.encode(&gray_frame(320, 240, 2)).unwrap();
896
897 assert_eq!(*log.lock().unwrap(), vec![false, true, false]);
898 }
899
900 #[test]
905 fn a_mid_stream_keyframe_request_emits_an_idr() {
906 let config = Config {
907 kind: Kind::Software,
908 ..Config::new(320, 240, 30)
909 };
910 let mut encoder = Encoder::new(&Config { gop: 1000, ..config }).unwrap();
913
914 let mut per_frame = Vec::new();
915 for i in 0..6 {
916 if i == 3 {
918 encoder.keyframe();
919 }
920 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
921 let joined: Vec<u8> = encoded.iter().flat_map(|f| f.payload.iter()).copied().collect();
922 per_frame.push(nal_types(&joined));
923 }
924
925 let asked = &per_frame[3];
928 assert!(asked.contains(&5), "the requested frame is not an IDR: {asked:?}");
929 assert!(asked.contains(&7), "no SPS with the requested IDR: {asked:?}");
930 assert!(asked.contains(&8), "no PPS with the requested IDR: {asked:?}");
931
932 for i in [1, 2, 4, 5] {
936 assert!(
937 !per_frame[i].contains(&5),
938 "frame {i} was keyed without being asked: {:?}",
939 per_frame[i]
940 );
941 }
942 }
943
944 struct Failing;
946
947 impl Backend for Failing {
948 fn encode(&mut self, _frame: &Frame, _keyframe: bool) -> Result<Vec<Encoded>, Error> {
949 Err(Error::Codec(anyhow::anyhow!("no")))
950 }
951
952 fn finish(&mut self) -> Result<Vec<Encoded>, Error> {
953 Ok(Vec::new())
954 }
955
956 fn set_bitrate(&mut self, _bitrate: u64) -> Result<(), Error> {
957 Ok(())
958 }
959
960 fn name(&self) -> &str {
961 "failing"
962 }
963 }
964
965 #[test]
970 fn a_failed_encode_keeps_the_keyframe_request() {
971 let config = Config::new(320, 240, 30);
972
973 let mut encoder = encoder_with(Box::new(Failing), &config);
974 encoder.keyframe();
975 assert!(encoder.encode(&gray_frame(320, 240, 0)).is_err());
976 assert!(encoder.pending_keyframe, "the backend error swallowed the request");
977
978 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
980 encoder.keyframe();
981 assert!(encoder.encode(&gray_frame(640, 480, 0)).is_err());
982 assert!(encoder.pending_keyframe, "the size check swallowed the request");
983
984 encoder.encode(&gray_frame(320, 240, 1)).unwrap();
986 assert!(!encoder.pending_keyframe);
987 }
988
989 #[test]
995 fn a_buffering_backend_keeps_each_frames_timestamp() {
996 let config = Config::new(320, 240, 30);
997 let mut encoder = encoder_with(Box::new(Delayed { pending: None }), &config);
998
999 for i in 0..5 {
1003 let encoded = encoder.encode(&gray_frame(320, 240, i)).unwrap();
1004 if i == 0 {
1005 assert!(encoded.is_empty(), "the first frame is still buffered");
1006 continue;
1007 }
1008 assert_eq!(encoded.len(), 1);
1009 assert_eq!(encoded[0].timestamp, at(i - 1));
1010 assert_eq!(&encoded[0].payload[..], at(i - 1).as_micros().to_string().as_bytes());
1013 }
1014
1015 let tail = encoder.finish().unwrap();
1018 assert_eq!(tail.len(), 1);
1019 assert_eq!(tail[0].timestamp, at(4));
1020 assert!(
1021 encoder_with(Box::new(Delayed { pending: None }), &config)
1022 .finish()
1023 .unwrap()
1024 .is_empty()
1025 );
1026 }
1027
1028 #[cfg(target_os = "macos")]
1033 #[test]
1034 fn videotoolbox_sps_declares_the_color_space() {
1035 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1036
1037 for (size, described) in [
1038 (Size::new(640, 480), BT601_DESCRIBED),
1039 (Size::new(1920, 1080), BT709_DESCRIBED),
1040 ] {
1041 let config = Config {
1042 kind: Kind::Named("videotoolbox".into()),
1043 ..Config::new(size.width, size.height, 30)
1044 };
1045 let mut encoder = Encoder::new(&config).expect("videotoolbox is available on macOS");
1046
1047 let rgba = [255u8, 0, 0, 255].repeat(size.pixels() as usize);
1048 let surface = crate::frame::Surface::rgba(&rgba, size).unwrap();
1049 encoder.keyframe();
1050 let frames = encoder
1051 .encode(&Frame::new(surface, moq_net::Timestamp::from_micros(0).unwrap()))
1052 .unwrap();
1053
1054 let keyframe = frames.first().expect("a keyframe");
1055 assert_eq!(declared_color(&keyframe.payload), Some(described), "{size} SPS");
1056 }
1057 }
1058
1059 #[test]
1064 fn config_color_pins_the_space_a_resize_carried() {
1065 use crate::Color;
1066
1067 let big = Size::new(1280, 720);
1068 let small = Size::new(640, 480);
1069
1070 let rgba = vec![0x80u8; big.pixels() as usize * 4];
1072 let frame = Frame::new(
1073 crate::frame::Surface::rgba(&rgba, big).unwrap(),
1074 moq_net::Timestamp::from_micros(0).unwrap(),
1075 );
1076 let scaled = frame.resize(small).unwrap();
1077 assert_eq!(
1078 scaled.surface.color(),
1079 Some(Color::Bt709Limited),
1080 "resize keeps the space"
1081 );
1082
1083 let config = Config {
1087 kind: Kind::Software,
1088 ..Config::new(small.width, small.height, 30)
1089 };
1090 let mut encoder = Encoder::new(&config).unwrap();
1091 encoder.keyframe();
1092 let frames = encoder.encode(&scaled).expect("a mismatch warns rather than fails");
1093 use super::backend::test_util::{BT601_DESCRIBED, BT709_DESCRIBED, declared_color};
1094 assert_eq!(
1095 declared_color(&frames.first().expect("a keyframe").payload),
1096 Some(BT601_DESCRIBED),
1097 "the inferred label is the wrong one, which is the case Config::color covers"
1098 );
1099
1100 let config = Config {
1102 kind: Kind::Software,
1103 color: Some(Color::Bt709Limited),
1104 ..Config::new(small.width, small.height, 30)
1105 };
1106 let mut encoder = Encoder::new(&config).unwrap();
1107 encoder.keyframe();
1108 let frames = encoder.encode(&scaled).expect("a declared space encodes");
1109
1110 let keyframe = frames.first().expect("a keyframe");
1111 assert_eq!(declared_color(&keyframe.payload), Some(BT709_DESCRIBED));
1112 }
1113}