1pub use mediaframe::frame::{Dimensions, Plane, Rect};
33
34use derive_more::IsVariant;
35use thiserror::Error;
36
37use crate::{Timestamp, color::ColorInfo, subtitle::SubtitlePayload};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, Error)]
41#[non_exhaustive]
42pub enum FrameError {
43 #[error(transparent)]
53 TooManyVideoPlanes(#[from] TooManyVideoPlanes),
54 #[error(transparent)]
60 TooManyAudioPlanes(#[from] TooManyAudioPlanes),
61 #[error(transparent)]
68 TooManyImagePlanes(#[from] TooManyImagePlanes),
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
73#[error("VideoFrame: plane_count {plane_count} exceeds the fixed 4-plane array")]
74pub struct TooManyVideoPlanes {
75 plane_count: u8,
77}
78
79impl TooManyVideoPlanes {
80 #[inline]
82 pub const fn new(plane_count: u8) -> Self {
83 Self { plane_count }
84 }
85 #[inline]
87 pub const fn plane_count(&self) -> u8 {
88 self.plane_count
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
94#[error("AudioFrame: plane_count {plane_count} exceeds the fixed 8-plane array")]
95pub struct TooManyAudioPlanes {
96 plane_count: u8,
98}
99
100impl TooManyAudioPlanes {
101 #[inline]
103 pub const fn new(plane_count: u8) -> Self {
104 Self { plane_count }
105 }
106 #[inline]
108 pub const fn plane_count(&self) -> u8 {
109 self.plane_count
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
115#[error("ImageFrame: plane_count {plane_count} exceeds the fixed 4-plane array")]
116pub struct TooManyImagePlanes {
117 plane_count: u8,
119}
120
121impl TooManyImagePlanes {
122 #[inline]
124 pub const fn new(plane_count: u8) -> Self {
125 Self { plane_count }
126 }
127 #[inline]
129 pub const fn plane_count(&self) -> u8 {
130 self.plane_count
131 }
132}
133
134#[derive(Clone)]
158pub struct VideoFrame<P, E, D> {
159 pts: Option<Timestamp>,
160 duration: Option<Timestamp>,
161 dimensions: Dimensions,
162 visible_rect: Option<Rect>,
163 pixel_format: P,
164 plane_count: u8,
165 planes: [Plane<D>; 4],
166 color: ColorInfo,
167 extra: E,
168}
169
170impl<P, E, D> VideoFrame<P, E, D> {
171 #[cfg_attr(not(tarpaulin), inline(always))]
187 pub const fn new(
188 dimensions: Dimensions,
189 pixel_format: P,
190 planes: [Plane<D>; 4],
191 plane_count: u8,
192 extra: E,
193 ) -> Self {
194 assert!(
195 plane_count as usize <= 4,
196 "VideoFrame::new: plane_count exceeds the fixed 4-plane array",
197 );
198 Self {
199 pts: None,
200 duration: None,
201 dimensions,
202 visible_rect: None,
203 pixel_format,
204 plane_count,
205 planes,
206 color: ColorInfo::UNSPECIFIED,
207 extra,
208 }
209 }
210
211 #[cfg_attr(not(tarpaulin), inline(always))]
220 pub fn try_new(
221 dimensions: Dimensions,
222 pixel_format: P,
223 planes: [Plane<D>; 4],
224 plane_count: u8,
225 extra: E,
226 ) -> Result<Self, FrameError> {
227 if plane_count as usize > 4 {
228 return Err(FrameError::TooManyVideoPlanes(TooManyVideoPlanes::new(
229 plane_count,
230 )));
231 }
232 Ok(Self {
233 pts: None,
234 duration: None,
235 dimensions,
236 visible_rect: None,
237 pixel_format,
238 plane_count,
239 planes,
240 color: ColorInfo::UNSPECIFIED,
241 extra,
242 })
243 }
244
245 #[cfg_attr(not(tarpaulin), inline(always))]
247 pub const fn pts(&self) -> Option<Timestamp> {
248 self.pts
249 }
250 #[cfg_attr(not(tarpaulin), inline(always))]
252 pub const fn duration(&self) -> Option<Timestamp> {
253 self.duration
254 }
255 #[cfg_attr(not(tarpaulin), inline(always))]
257 pub const fn dimensions(&self) -> Dimensions {
258 self.dimensions
259 }
260 #[cfg_attr(not(tarpaulin), inline(always))]
262 pub const fn width(&self) -> u32 {
263 self.dimensions.width()
264 }
265 #[cfg_attr(not(tarpaulin), inline(always))]
267 pub const fn height(&self) -> u32 {
268 self.dimensions.height()
269 }
270 #[cfg_attr(not(tarpaulin), inline(always))]
272 pub const fn visible_rect(&self) -> Option<Rect> {
273 self.visible_rect
274 }
275 #[cfg_attr(not(tarpaulin), inline(always))]
277 pub const fn pixel_format(&self) -> &P {
278 &self.pixel_format
279 }
280 #[cfg_attr(not(tarpaulin), inline(always))]
282 pub const fn plane_count(&self) -> u8 {
283 self.plane_count
284 }
285 #[cfg_attr(not(tarpaulin), inline(always))]
287 pub fn planes(&self) -> &[Plane<D>] {
288 &self.planes[..self.plane_count as usize]
289 }
290 #[cfg_attr(not(tarpaulin), inline(always))]
292 pub fn plane(&self, i: usize) -> Option<&Plane<D>> {
293 if i < self.plane_count as usize {
294 self.planes.get(i)
295 } else {
296 None
297 }
298 }
299 #[cfg_attr(not(tarpaulin), inline(always))]
306 pub fn color(&self) -> ColorInfo {
307 self.color.clone()
308 }
309 #[cfg_attr(not(tarpaulin), inline(always))]
311 pub const fn extra(&self) -> &E {
312 &self.extra
313 }
314 #[cfg_attr(not(tarpaulin), inline(always))]
316 pub const fn extra_mut(&mut self) -> &mut E {
317 &mut self.extra
318 }
319
320 #[cfg_attr(not(tarpaulin), inline(always))]
322 #[must_use]
323 pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
324 self.pts = v;
325 self
326 }
327 #[cfg_attr(not(tarpaulin), inline(always))]
329 #[must_use]
330 pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
331 self.duration = v;
332 self
333 }
334 #[cfg_attr(not(tarpaulin), inline(always))]
336 #[must_use]
337 pub const fn with_visible_rect(mut self, v: Option<Rect>) -> Self {
338 self.visible_rect = v;
339 self
340 }
341 #[cfg_attr(not(tarpaulin), inline(always))]
351 #[must_use]
352 pub fn with_color(mut self, v: ColorInfo) -> Self {
353 self.color = v;
354 self
355 }
356
357 #[cfg_attr(not(tarpaulin), inline(always))]
359 pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
360 self.pts = v;
361 self
362 }
363 #[cfg_attr(not(tarpaulin), inline(always))]
365 pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
366 self.duration = v;
367 self
368 }
369 #[cfg_attr(not(tarpaulin), inline(always))]
371 pub const fn set_visible_rect(&mut self, v: Option<Rect>) -> &mut Self {
372 self.visible_rect = v;
373 self
374 }
375 #[cfg_attr(not(tarpaulin), inline(always))]
379 pub fn set_color(&mut self, v: ColorInfo) -> &mut Self {
380 self.color = v;
381 self
382 }
383}
384
385#[derive(Clone)]
400pub struct AudioFrame<S, C, E, D> {
401 pts: Option<Timestamp>,
402 duration: Option<Timestamp>,
403 sample_rate: u32,
404 nb_samples: u32,
405 channel_count: u8,
406 sample_format: S,
407 channel_layout: C,
408 plane_count: u8,
409 planes: [Plane<D>; 8],
410 extra: E,
411}
412
413impl<S, C, E, D> AudioFrame<S, C, E, D> {
414 #[allow(clippy::too_many_arguments)]
426 #[cfg_attr(not(tarpaulin), inline(always))]
427 pub const fn new(
428 sample_rate: u32,
429 nb_samples: u32,
430 channel_count: u8,
431 sample_format: S,
432 channel_layout: C,
433 planes: [Plane<D>; 8],
434 plane_count: u8,
435 extra: E,
436 ) -> Self {
437 assert!(
438 plane_count as usize <= 8,
439 "AudioFrame::new: plane_count exceeds the fixed 8-plane array",
440 );
441 Self {
442 pts: None,
443 duration: None,
444 sample_rate,
445 nb_samples,
446 channel_count,
447 sample_format,
448 channel_layout,
449 plane_count,
450 planes,
451 extra,
452 }
453 }
454
455 #[allow(clippy::too_many_arguments)]
462 #[cfg_attr(not(tarpaulin), inline(always))]
463 pub fn try_new(
464 sample_rate: u32,
465 nb_samples: u32,
466 channel_count: u8,
467 sample_format: S,
468 channel_layout: C,
469 planes: [Plane<D>; 8],
470 plane_count: u8,
471 extra: E,
472 ) -> Result<Self, FrameError> {
473 if plane_count as usize > 8 {
474 return Err(FrameError::TooManyAudioPlanes(TooManyAudioPlanes::new(
475 plane_count,
476 )));
477 }
478 Ok(Self {
479 pts: None,
480 duration: None,
481 sample_rate,
482 nb_samples,
483 channel_count,
484 sample_format,
485 channel_layout,
486 plane_count,
487 planes,
488 extra,
489 })
490 }
491
492 #[cfg_attr(not(tarpaulin), inline(always))]
494 pub const fn pts(&self) -> Option<Timestamp> {
495 self.pts
496 }
497 #[cfg_attr(not(tarpaulin), inline(always))]
499 pub const fn duration(&self) -> Option<Timestamp> {
500 self.duration
501 }
502 #[cfg_attr(not(tarpaulin), inline(always))]
504 pub const fn sample_rate(&self) -> u32 {
505 self.sample_rate
506 }
507 #[cfg_attr(not(tarpaulin), inline(always))]
509 pub const fn nb_samples(&self) -> u32 {
510 self.nb_samples
511 }
512 #[cfg_attr(not(tarpaulin), inline(always))]
514 pub const fn channel_count(&self) -> u8 {
515 self.channel_count
516 }
517 #[cfg_attr(not(tarpaulin), inline(always))]
519 pub const fn sample_format(&self) -> &S {
520 &self.sample_format
521 }
522 #[cfg_attr(not(tarpaulin), inline(always))]
524 pub const fn channel_layout(&self) -> &C {
525 &self.channel_layout
526 }
527 #[cfg_attr(not(tarpaulin), inline(always))]
529 pub const fn plane_count(&self) -> u8 {
530 self.plane_count
531 }
532 #[cfg_attr(not(tarpaulin), inline(always))]
534 pub fn planes(&self) -> &[Plane<D>] {
535 &self.planes[..self.plane_count as usize]
536 }
537 #[cfg_attr(not(tarpaulin), inline(always))]
539 pub const fn extra(&self) -> &E {
540 &self.extra
541 }
542 #[cfg_attr(not(tarpaulin), inline(always))]
544 pub const fn extra_mut(&mut self) -> &mut E {
545 &mut self.extra
546 }
547
548 #[cfg_attr(not(tarpaulin), inline(always))]
550 #[must_use]
551 pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
552 self.pts = v;
553 self
554 }
555 #[cfg_attr(not(tarpaulin), inline(always))]
557 #[must_use]
558 pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
559 self.duration = v;
560 self
561 }
562
563 #[cfg_attr(not(tarpaulin), inline(always))]
565 pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
566 self.pts = v;
567 self
568 }
569 #[cfg_attr(not(tarpaulin), inline(always))]
571 pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
572 self.duration = v;
573 self
574 }
575}
576
577#[derive(Clone)]
585pub struct SubtitleFrame<E, D> {
586 pts: Option<Timestamp>,
587 duration: Option<Timestamp>,
588 payload: SubtitlePayload<D>,
589 extra: E,
590}
591
592impl<E, D> SubtitleFrame<E, D> {
593 #[cfg_attr(not(tarpaulin), inline(always))]
595 pub const fn new(payload: SubtitlePayload<D>, extra: E) -> Self {
596 Self {
597 pts: None,
598 duration: None,
599 payload,
600 extra,
601 }
602 }
603
604 #[cfg_attr(not(tarpaulin), inline(always))]
606 pub const fn pts(&self) -> Option<Timestamp> {
607 self.pts
608 }
609 #[cfg_attr(not(tarpaulin), inline(always))]
611 pub const fn duration(&self) -> Option<Timestamp> {
612 self.duration
613 }
614 #[cfg_attr(not(tarpaulin), inline(always))]
616 pub const fn payload(&self) -> &SubtitlePayload<D> {
617 &self.payload
618 }
619 #[cfg_attr(not(tarpaulin), inline(always))]
621 pub const fn extra(&self) -> &E {
622 &self.extra
623 }
624 #[cfg_attr(not(tarpaulin), inline(always))]
626 pub const fn extra_mut(&mut self) -> &mut E {
627 &mut self.extra
628 }
629
630 #[cfg_attr(not(tarpaulin), inline(always))]
632 #[must_use]
633 pub const fn with_pts(mut self, v: Option<Timestamp>) -> Self {
634 self.pts = v;
635 self
636 }
637 #[cfg_attr(not(tarpaulin), inline(always))]
639 #[must_use]
640 pub const fn with_duration(mut self, v: Option<Timestamp>) -> Self {
641 self.duration = v;
642 self
643 }
644
645 #[cfg_attr(not(tarpaulin), inline(always))]
647 pub const fn set_pts(&mut self, v: Option<Timestamp>) -> &mut Self {
648 self.pts = v;
649 self
650 }
651 #[cfg_attr(not(tarpaulin), inline(always))]
653 pub const fn set_duration(&mut self, v: Option<Timestamp>) -> &mut Self {
654 self.duration = v;
655 self
656 }
657}
658
659#[derive(Clone)]
685pub struct ImageFrame<P, E, D> {
686 dimensions: Dimensions,
687 visible_rect: Option<Rect>,
688 pixel_format: P,
689 plane_count: u8,
690 planes: [Plane<D>; 4],
691 color: ColorInfo,
692 extra: E,
693}
694
695impl<P, E, D> ImageFrame<P, E, D> {
696 #[cfg_attr(not(tarpaulin), inline(always))]
712 pub const fn new(
713 dimensions: Dimensions,
714 pixel_format: P,
715 planes: [Plane<D>; 4],
716 plane_count: u8,
717 extra: E,
718 ) -> Self {
719 assert!(
720 plane_count as usize <= 4,
721 "ImageFrame::new: plane_count exceeds the fixed 4-plane array",
722 );
723 Self {
724 dimensions,
725 visible_rect: None,
726 pixel_format,
727 plane_count,
728 planes,
729 color: ColorInfo::UNSPECIFIED,
730 extra,
731 }
732 }
733
734 #[cfg_attr(not(tarpaulin), inline(always))]
740 pub fn try_new(
741 dimensions: Dimensions,
742 pixel_format: P,
743 planes: [Plane<D>; 4],
744 plane_count: u8,
745 extra: E,
746 ) -> Result<Self, FrameError> {
747 if plane_count as usize > 4 {
748 return Err(FrameError::TooManyImagePlanes(TooManyImagePlanes::new(
749 plane_count,
750 )));
751 }
752 Ok(Self {
753 dimensions,
754 visible_rect: None,
755 pixel_format,
756 plane_count,
757 planes,
758 color: ColorInfo::UNSPECIFIED,
759 extra,
760 })
761 }
762
763 #[cfg_attr(not(tarpaulin), inline(always))]
765 pub const fn dimensions(&self) -> Dimensions {
766 self.dimensions
767 }
768 #[cfg_attr(not(tarpaulin), inline(always))]
770 pub const fn width(&self) -> u32 {
771 self.dimensions.width()
772 }
773 #[cfg_attr(not(tarpaulin), inline(always))]
775 pub const fn height(&self) -> u32 {
776 self.dimensions.height()
777 }
778 #[cfg_attr(not(tarpaulin), inline(always))]
780 pub const fn visible_rect(&self) -> Option<Rect> {
781 self.visible_rect
782 }
783 #[cfg_attr(not(tarpaulin), inline(always))]
785 pub const fn pixel_format(&self) -> &P {
786 &self.pixel_format
787 }
788 #[cfg_attr(not(tarpaulin), inline(always))]
790 pub const fn plane_count(&self) -> u8 {
791 self.plane_count
792 }
793 #[cfg_attr(not(tarpaulin), inline(always))]
795 pub fn planes(&self) -> &[Plane<D>] {
796 &self.planes[..self.plane_count as usize]
797 }
798 #[cfg_attr(not(tarpaulin), inline(always))]
800 pub fn plane(&self, i: usize) -> Option<&Plane<D>> {
801 if i < self.plane_count as usize {
802 self.planes.get(i)
803 } else {
804 None
805 }
806 }
807 #[cfg_attr(not(tarpaulin), inline(always))]
811 pub fn color(&self) -> ColorInfo {
812 self.color.clone()
813 }
814 #[cfg_attr(not(tarpaulin), inline(always))]
816 pub const fn extra(&self) -> &E {
817 &self.extra
818 }
819 #[cfg_attr(not(tarpaulin), inline(always))]
821 pub const fn extra_mut(&mut self) -> &mut E {
822 &mut self.extra
823 }
824
825 #[cfg_attr(not(tarpaulin), inline(always))]
827 #[must_use]
828 pub const fn with_visible_rect(mut self, v: Option<Rect>) -> Self {
829 self.visible_rect = v;
830 self
831 }
832 #[cfg_attr(not(tarpaulin), inline(always))]
836 #[must_use]
837 pub fn with_color(mut self, v: ColorInfo) -> Self {
838 self.color = v;
839 self
840 }
841
842 #[cfg_attr(not(tarpaulin), inline(always))]
844 pub const fn set_visible_rect(&mut self, v: Option<Rect>) -> &mut Self {
845 self.visible_rect = v;
846 self
847 }
848 #[cfg_attr(not(tarpaulin), inline(always))]
852 pub fn set_color(&mut self, v: ColorInfo) -> &mut Self {
853 self.color = v;
854 self
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 use crate::{
863 color::{ColorInfo, ColorMatrix},
864 subtitle::SubtitlePayload,
865 };
866
867 fn empty_planes() -> [Plane<&'static [u8]>; 4] {
868 [
869 Plane::new(&[][..], 0),
870 Plane::new(&[][..], 0),
871 Plane::new(&[][..], 0),
872 Plane::new(&[][..], 0),
873 ]
874 }
875
876 #[test]
877 fn rect_construct_and_access() {
878 let r = Rect::new(10, 20, 1920, 1080);
879 assert_eq!(r.x(), 10);
880 assert_eq!(r.y(), 20);
881 assert_eq!(r.width(), 1920);
882 assert_eq!(r.height(), 1080);
883 }
884
885 #[test]
886 fn rect_default_is_zero() {
887 let r = Rect::default();
888 assert_eq!((r.x(), r.y(), r.width(), r.height()), (0, 0, 0, 0));
889 }
890
891 #[test]
892 fn rect_builders_chain() {
893 let r = Rect::default()
894 .with_x(1)
895 .with_y(2)
896 .with_width(3)
897 .with_height(4);
898 assert_eq!((r.x(), r.y(), r.width(), r.height()), (1, 2, 3, 4));
899 }
900
901 #[test]
902 fn rect_setters_chain() {
903 let mut r = Rect::default();
904 r.set_x(5).set_y(6).set_width(7).set_height(8);
905 assert_eq!((r.x(), r.y(), r.width(), r.height()), (5, 6, 7, 8));
906 }
907
908 #[test]
909 fn rect_const_construction() {
910 const R: Rect = Rect::new(0, 0, 1920, 1080);
911 assert_eq!(R.width(), 1920);
912 }
913
914 #[test]
915 fn plane_construct_and_access_borrowed() {
916 let buf: [u8; 4] = [1, 2, 3, 4];
917 let p: Plane<&[u8]> = Plane::new(&buf, 4);
918 assert_eq!(p.stride(), 4);
919 assert_eq!(p.data_ref(), &&buf[..]);
920 }
921
922 #[test]
923 fn plane_with_and_set_stride() {
924 let buf: [u8; 0] = [];
925 let p = Plane::new(&buf[..], 16).with_stride(32);
926 assert_eq!(p.stride(), 32);
927 let mut p2 = p;
928 p2.set_stride(64);
929 assert_eq!(p2.stride(), 64);
930 }
931
932 #[test]
933 fn plane_into_data() {
934 let buf: [u8; 4] = [1, 2, 3, 4];
935 let p: Plane<&[u8]> = Plane::new(&buf, 4);
936 let recovered = p.into_data();
937 assert_eq!(recovered, &buf[..]);
938 }
939
940 #[test]
941 fn video_frame_construct_and_access() {
942 let f: VideoFrame<u32, (), &[u8]> = VideoFrame::new(
945 Dimensions::new(1920, 1080),
946 0u32,
947 empty_planes(),
948 1,
949 (),
950 );
951 assert_eq!(f.width(), 1920);
952 assert_eq!(f.height(), 1080);
953 assert_eq!(f.dimensions(), Dimensions::new(1920, 1080));
954 assert_eq!(f.plane_count(), 1);
955 assert_eq!(f.color().matrix(), crate::color::ColorMatrix::Unspecified);
957 assert_eq!(f.planes().len(), 1);
958 }
959
960 #[test]
961 fn video_frame_plane_index_clamped() {
962 let f: VideoFrame<u32, (), &[u8]> =
963 VideoFrame::new(Dimensions::new(64, 64), 0u32, empty_planes(), 2, ());
964 assert!(f.plane(0).is_some());
965 assert!(f.plane(1).is_some());
966 assert!(f.plane(2).is_none());
967 assert!(f.plane(3).is_none());
968 }
969
970 #[test]
971 fn video_frame_builders_chain() {
972 let ci = ColorInfo::UNSPECIFIED.with_matrix(ColorMatrix::Bt2020Ncl);
973 let f: VideoFrame<u32, (), &[u8]> =
974 VideoFrame::new(Dimensions::new(64, 64), 0u32, empty_planes(), 1, ())
975 .with_color(ci)
976 .with_visible_rect(Some(Rect::new(0, 0, 64, 64)));
977 assert!(f.color().matrix().is_bt_2020_ncl());
978 assert!(f.visible_rect().is_some());
979 }
980
981 fn audio_planes() -> [Plane<&'static [u8]>; 8] {
982 [
983 Plane::new(&[][..], 0),
984 Plane::new(&[][..], 0),
985 Plane::new(&[][..], 0),
986 Plane::new(&[][..], 0),
987 Plane::new(&[][..], 0),
988 Plane::new(&[][..], 0),
989 Plane::new(&[][..], 0),
990 Plane::new(&[][..], 0),
991 ]
992 }
993
994 #[test]
995 #[should_panic(expected = "plane_count exceeds the fixed 4-plane array")]
996 fn video_frame_rejects_plane_count_above_array_size() {
997 let _f: VideoFrame<u32, (), &[u8]> =
998 VideoFrame::new(Dimensions::new(64, 64), 0u32, empty_planes(), 5, ());
999 }
1000
1001 #[test]
1002 fn video_frame_try_new_returns_err_for_too_many_planes() {
1003 let res: Result<VideoFrame<u32, (), &[u8]>, FrameError> =
1004 VideoFrame::try_new(Dimensions::new(64, 64), 0u32, empty_planes(), 5, ());
1005 assert!(matches!(
1006 res,
1007 Err(FrameError::TooManyVideoPlanes(p)) if p.plane_count() == 5,
1008 ));
1009 }
1010
1011 #[test]
1012 fn video_frame_try_new_accepts_valid_plane_count() {
1013 let f: VideoFrame<u32, (), &[u8]> =
1014 VideoFrame::try_new(Dimensions::new(64, 64), 0u32, empty_planes(), 2, ())
1015 .expect("plane_count = 2 is within the 4-slot capacity");
1016 assert_eq!(f.plane_count(), 2);
1017 }
1018
1019 #[test]
1020 #[should_panic(expected = "plane_count exceeds the fixed 8-plane array")]
1021 fn audio_frame_rejects_plane_count_above_array_size() {
1022 let _f: AudioFrame<u32, u32, (), &[u8]> =
1023 AudioFrame::new(48_000, 1024, 2, 0u32, 0u32, audio_planes(), 9, ());
1024 }
1025
1026 #[test]
1027 fn audio_frame_try_new_returns_err_for_too_many_planes() {
1028 let res: Result<AudioFrame<u32, u32, (), &[u8]>, FrameError> =
1029 AudioFrame::try_new(48_000, 1024, 2, 0u32, 0u32, audio_planes(), 9, ());
1030 assert!(matches!(
1031 res,
1032 Err(FrameError::TooManyAudioPlanes(p)) if p.plane_count() == 9,
1033 ));
1034 }
1035
1036 #[test]
1037 fn audio_frame_try_new_accepts_valid_plane_count() {
1038 let f: AudioFrame<u32, u32, (), &[u8]> =
1039 AudioFrame::try_new(48_000, 1024, 2, 0u32, 0u32, audio_planes(), 8, ())
1040 .expect("plane_count = 8 is the 8-slot capacity boundary");
1041 assert_eq!(f.plane_count(), 8);
1042 }
1043
1044 #[test]
1045 fn audio_frame_construct_and_access() {
1046 let f: AudioFrame<u32, u32, (), &[u8]> = AudioFrame::new(
1049 48_000,
1050 1024,
1051 2,
1052 0u32,
1053 0u32,
1054 audio_planes(),
1055 2,
1056 (),
1057 );
1058 assert_eq!(f.sample_rate(), 48_000);
1059 assert_eq!(f.nb_samples(), 1024);
1060 assert_eq!(f.channel_count(), 2);
1061 assert_eq!(f.plane_count(), 2);
1062 assert_eq!(f.planes().len(), 2);
1063 }
1064
1065 #[test]
1066 fn image_frame_construct_and_access() {
1067 let f: ImageFrame<u32, (), &[u8]> = ImageFrame::new(
1070 Dimensions::new(32, 32),
1071 0u32,
1072 empty_planes(),
1073 3,
1074 (),
1075 );
1076 assert_eq!(f.width(), 32);
1077 assert_eq!(f.height(), 32);
1078 assert_eq!(f.dimensions(), Dimensions::new(32, 32));
1079 assert_eq!(f.plane_count(), 3);
1080 assert_eq!(f.planes().len(), 3);
1081 assert_eq!(f.color().matrix(), ColorMatrix::Unspecified);
1082 assert!(f.visible_rect().is_none());
1083 }
1084
1085 #[test]
1086 fn image_frame_plane_index_clamped() {
1087 let f: ImageFrame<u32, (), &[u8]> =
1088 ImageFrame::new(Dimensions::new(8, 8), 0u32, empty_planes(), 1, ());
1089 assert!(f.plane(0).is_some());
1090 assert!(f.plane(1).is_none());
1091 assert!(f.plane(3).is_none());
1092 }
1093
1094 #[test]
1095 fn image_frame_builders_and_setters_chain() {
1096 let ci = ColorInfo::UNSPECIFIED.with_matrix(ColorMatrix::Bt601);
1099 let mut f: ImageFrame<u32, (), &[u8]> =
1100 ImageFrame::new(Dimensions::new(32, 32), 0u32, empty_planes(), 1, ())
1101 .with_visible_rect(Some(Rect::new(0, 0, 30, 30)))
1102 .with_color(ci);
1103 assert_eq!(f.visible_rect(), Some(Rect::new(0, 0, 30, 30)));
1104 assert_eq!(f.color().matrix(), ColorMatrix::Bt601);
1105 f.set_visible_rect(None)
1106 .set_color(ColorInfo::UNSPECIFIED.with_matrix(ColorMatrix::Bt709));
1107 assert!(f.visible_rect().is_none());
1108 assert_eq!(f.color().matrix(), ColorMatrix::Bt709);
1109 }
1110
1111 #[test]
1112 #[should_panic(expected = "plane_count exceeds the fixed 4-plane array")]
1113 fn image_frame_rejects_plane_count_above_array_size() {
1114 let _f: ImageFrame<u32, (), &[u8]> =
1115 ImageFrame::new(Dimensions::new(8, 8), 0u32, empty_planes(), 5, ());
1116 }
1117
1118 #[test]
1119 fn image_frame_try_new_returns_err_for_too_many_planes() {
1120 let res: Result<ImageFrame<u32, (), &[u8]>, FrameError> =
1121 ImageFrame::try_new(Dimensions::new(8, 8), 0u32, empty_planes(), 5, ());
1122 assert!(matches!(
1123 res,
1124 Err(FrameError::TooManyImagePlanes(p)) if p.plane_count() == 5,
1125 ));
1126 }
1127
1128 #[test]
1129 fn image_frame_try_new_accepts_the_capacity_boundary() {
1130 let f: ImageFrame<u32, (), &[u8]> =
1131 ImageFrame::try_new(Dimensions::new(8, 8), 0u32, empty_planes(), 4, ())
1132 .expect("plane_count = 4 is the 4-slot capacity boundary");
1133 assert_eq!(f.plane_count(), 4);
1134 }
1135
1136 #[test]
1137 fn image_frame_extra_is_reachable_by_reference_and_by_mutation() {
1138 let mut f: ImageFrame<u32, u8, &[u8]> =
1139 ImageFrame::new(Dimensions::new(1, 1), 0u32, empty_planes(), 1, 7u8);
1140 assert_eq!(*f.extra(), 7);
1141 *f.extra_mut() = 9;
1142 assert_eq!(*f.extra(), 9);
1143 }
1144
1145 #[test]
1146 fn every_frame_household_clones() {
1147 fn clones<T: Clone>(_: &T) {}
1151 let v: VideoFrame<u32, (), &[u8]> =
1152 VideoFrame::new(Dimensions::new(2, 2), 0u32, empty_planes(), 1, ());
1153 let a: AudioFrame<u32, u32, (), &[u8]> =
1154 AudioFrame::new(48_000, 1, 1, 0u32, 0u32, audio_planes(), 1, ());
1155 let s: SubtitleFrame<(), &[u8]> = SubtitleFrame::new(
1156 SubtitlePayload::Text(crate::subtitle::Text::new(&[][..], None)),
1157 (),
1158 );
1159 let i: ImageFrame<u32, (), &[u8]> =
1160 ImageFrame::new(Dimensions::new(2, 2), 0u32, empty_planes(), 1, ());
1161 clones(&v.clone());
1162 clones(&a.clone());
1163 clones(&s.clone());
1164 clones(&i.clone());
1165 assert_eq!(v.clone().plane_count(), v.plane_count());
1166 assert_eq!(i.clone().dimensions(), i.dimensions());
1167 }
1168
1169 #[test]
1170 fn subtitle_frame_text_payload() {
1171 let payload: SubtitlePayload<&[u8]> =
1172 SubtitlePayload::Text(crate::subtitle::Text::new(b"hi", None));
1173 let f: SubtitleFrame<(), &[u8]> = SubtitleFrame::new(payload, ());
1175 match f.payload() {
1176 SubtitlePayload::Text(p) => assert_eq!(p.text(), &&b"hi"[..]),
1177 #[cfg(any(feature = "std", feature = "alloc"))]
1178 _ => panic!("unexpected variant"),
1179 }
1180 }
1181}