1use std::ops::{Index, IndexMut};
2use std::ptr::NonNull;
3
4use crate::consts::{Const, Mut};
5use crate::error::{FfmpegError, FfmpegErrorCode};
6use crate::ffi::*;
7use crate::rational::Rational;
8use crate::smart_object::{SmartObject, SmartPtr};
9use crate::utils::{check_i64, or_nopts};
10use crate::{AVPictureType, AVPixelFormat, AVSampleFormat};
11
12#[derive(Debug, PartialEq)]
14pub struct FrameData {
15 ptr: NonNull<u8>,
17 linesize: i32,
18 height: i32,
19}
20
21impl core::ops::Index<usize> for FrameData {
22 type Output = u8;
23
24 fn index(&self, index: usize) -> &Self::Output {
25 if index >= self.len() {
26 panic!("index out of bounds: the len is {} but the index is {}", self.len(), index);
27 }
28 if self.linesize.is_positive() {
29 let ptr = unsafe { self.ptr.byte_add(index) };
31 unsafe { ptr.as_ref() }
33 } else {
34 let stride = self.linesize.unsigned_abs() as usize;
35 let line = index / stride;
36 let line_pos = index % stride;
37 let current_line_ptr = unsafe { self.ptr.byte_sub(line * stride) };
39 let value_ptr = unsafe { current_line_ptr.byte_add(line_pos) };
41 unsafe { value_ptr.as_ref() }
43 }
44 }
45}
46
47impl core::ops::IndexMut<usize> for FrameData {
48 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
49 if index >= self.len() {
50 panic!("index out of bounds: the len is {} but the index is {}", self.len(), index);
51 }
52 if self.linesize.is_positive() {
53 let mut ptr = unsafe { self.ptr.byte_add(index) };
55 unsafe { ptr.as_mut() }
57 } else {
58 let stride = self.linesize.unsigned_abs() as usize;
59 let line = index / stride;
60 let line_pos = index % stride;
61 let current_line_ptr = unsafe { self.ptr.byte_sub(line * stride) };
63 let mut value_ptr = unsafe { current_line_ptr.byte_add(line_pos) };
65 unsafe { value_ptr.as_mut() }
67 }
68 }
69}
70
71impl FrameData {
72 pub const fn height(&self) -> i32 {
74 self.height
75 }
76
77 pub const fn linesize(&self) -> i32 {
80 self.linesize
81 }
82
83 pub const fn len(&self) -> usize {
85 (self.linesize.abs() * self.height) as usize
86 }
87
88 pub const fn is_empty(&self) -> bool {
90 self.len() == 0
91 }
92
93 pub fn get(&self, index: usize) -> Option<&u8> {
95 if index < self.len() { Some(self.index(index)) } else { None }
96 }
97
98 pub fn get_mut(&mut self, index: usize) -> Option<&mut u8> {
100 if index < self.len() {
101 Some(self.index_mut(index))
102 } else {
103 None
104 }
105 }
106
107 pub const fn get_row(&self, index: usize) -> Option<&[u8]> {
109 if index >= self.height as usize {
110 return None;
111 }
112
113 let start_ptr = unsafe { self.ptr.byte_offset(self.linesize as isize * index as isize) };
115 Some(unsafe { core::slice::from_raw_parts(start_ptr.as_ptr(), self.linesize.unsigned_abs() as usize) })
117 }
118
119 pub const fn get_row_mut(&mut self, index: usize) -> Option<&mut [u8]> {
121 if index >= self.height() as usize {
122 return None;
123 }
124
125 let start_ptr = unsafe { self.ptr.byte_offset(self.linesize as isize * index as isize) };
127 Some(unsafe { core::slice::from_raw_parts_mut(start_ptr.as_ptr(), self.linesize.unsigned_abs() as usize) })
129 }
130
131 pub fn fill(&mut self, value: u8) {
133 for row in 0..self.height() {
134 let slice = self.get_row_mut(row as usize).expect("row is out of bounds");
135 slice.fill(value);
136 }
137 }
138}
139
140pub struct GenericFrame(SmartPtr<AVFrame>);
142
143impl Clone for GenericFrame {
144 fn clone(&self) -> Self {
145 let clone = unsafe { av_frame_clone(self.0.as_ptr()) };
147
148 unsafe { Self::wrap(clone).expect("failed to clone frame") }
150 }
151}
152
153unsafe impl Send for GenericFrame {}
155
156unsafe impl Sync for GenericFrame {}
158
159#[derive(Clone)]
161pub struct VideoFrame(GenericFrame);
162
163#[derive(Clone)]
165pub struct AudioFrame(GenericFrame);
166
167impl GenericFrame {
168 pub(crate) fn new() -> Result<Self, FfmpegError> {
170 let frame = unsafe { av_frame_alloc() };
172
173 unsafe { Self::wrap(frame).ok_or(FfmpegError::Alloc) }
175 }
176
177 pub(crate) unsafe fn wrap(ptr: *mut AVFrame) -> Option<Self> {
183 let destructor = |ptr: &mut *mut AVFrame| {
184 unsafe { av_frame_free(ptr) }
186 };
187
188 unsafe { SmartPtr::wrap_non_null(ptr, destructor).map(Self) }
190 }
191
192 pub(crate) unsafe fn alloc_frame_buffer(&mut self, alignment: Option<i32>) -> Result<(), FfmpegError> {
199 FfmpegErrorCode(unsafe { av_frame_get_buffer(self.as_mut_ptr(), alignment.unwrap_or(0)) }).result()?;
205 Ok(())
206 }
207
208 pub(crate) const fn as_ptr(&self) -> *const AVFrame {
210 self.0.as_ptr()
211 }
212
213 pub(crate) const fn as_mut_ptr(&mut self) -> *mut AVFrame {
215 self.0.as_mut_ptr()
216 }
217
218 pub(crate) const fn video(self) -> VideoFrame {
220 VideoFrame(self)
221 }
222
223 pub(crate) const fn audio(self) -> AudioFrame {
225 AudioFrame(self)
226 }
227
228 pub const fn pts(&self) -> Option<i64> {
230 check_i64(self.0.as_deref_except().pts)
231 }
232
233 pub const fn set_pts(&mut self, pts: Option<i64>) {
235 self.0.as_deref_mut_except().pts = or_nopts(pts);
236 self.0.as_deref_mut_except().best_effort_timestamp = or_nopts(pts);
237 }
238
239 pub const fn duration(&self) -> Option<i64> {
241 check_i64(self.0.as_deref_except().duration)
242 }
243
244 pub const fn set_duration(&mut self, duration: Option<i64>) {
246 self.0.as_deref_mut_except().duration = or_nopts(duration);
247 }
248
249 pub const fn best_effort_timestamp(&self) -> Option<i64> {
251 check_i64(self.0.as_deref_except().best_effort_timestamp)
252 }
253
254 pub const fn dts(&self) -> Option<i64> {
256 check_i64(self.0.as_deref_except().pkt_dts)
257 }
258
259 pub(crate) const fn set_dts(&mut self, dts: Option<i64>) {
261 self.0.as_deref_mut_except().pkt_dts = or_nopts(dts);
262 }
263
264 pub fn time_base(&self) -> Rational {
266 self.0.as_deref_except().time_base.into()
267 }
268
269 pub fn set_time_base(&mut self, time_base: impl Into<Rational>) {
271 self.0.as_deref_mut_except().time_base = time_base.into().into();
272 }
273
274 pub(crate) const fn format(&self) -> i32 {
276 self.0.as_deref_except().format
277 }
278
279 pub(crate) const fn is_audio(&self) -> bool {
281 self.0.as_deref_except().ch_layout.nb_channels != 0
282 }
283
284 pub(crate) const fn is_video(&self) -> bool {
286 self.0.as_deref_except().width != 0
287 }
288
289 pub const fn linesize(&self, index: usize) -> Option<i32> {
291 if index >= self.0.as_deref_except().linesize.len() {
292 return None;
293 }
294 Some(self.0.as_deref_except().linesize[index])
295 }
296}
297
298impl std::fmt::Debug for GenericFrame {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 f.debug_struct("GenericFrame")
301 .field("pts", &self.pts())
302 .field("dts", &self.dts())
303 .field("duration", &self.duration())
304 .field("best_effort_timestamp", &self.best_effort_timestamp())
305 .field("time_base", &self.time_base())
306 .field("format", &self.format())
307 .field("is_audio", &self.is_audio())
308 .field("is_video", &self.is_video())
309 .finish()
310 }
311}
312
313#[bon::bon]
314impl VideoFrame {
315 #[builder]
317 pub fn new(
318 width: i32,
319 height: i32,
320 pix_fmt: AVPixelFormat,
321 #[builder(default = Rational::ONE)] sample_aspect_ratio: Rational,
322 #[builder(default = AV_NOPTS_VALUE)] pts: i64,
323 #[builder(default = AV_NOPTS_VALUE)] dts: i64,
324 #[builder(default = 0)] duration: i64,
325 #[builder(default = Rational::ZERO)] time_base: Rational,
326 #[builder(default = 0)]
328 alignment: i32,
329 ) -> Result<Self, FfmpegError> {
330 if width <= 0 || height <= 0 {
331 return Err(FfmpegError::Arguments("width and height must be positive and not 0"));
332 }
333 if alignment < 0 {
334 return Err(FfmpegError::Arguments("alignment must be positive"));
335 }
336
337 let mut generic = GenericFrame::new()?;
338 let inner = generic.0.as_deref_mut_except();
339
340 inner.pict_type = AVPictureType::None.0 as _;
341 inner.width = width;
342 inner.height = height;
343 inner.format = pix_fmt.0;
344 inner.pts = pts;
345 inner.best_effort_timestamp = pts;
346 inner.pkt_dts = dts;
347 inner.duration = duration;
348 inner.time_base = time_base.into();
349 inner.sample_aspect_ratio = sample_aspect_ratio.into();
350
351 unsafe { generic.alloc_frame_buffer(Some(alignment))? };
353
354 Ok(VideoFrame(generic))
355 }
356
357 pub const fn width(&self) -> usize {
359 self.0.0.as_deref_except().width as usize
360 }
361
362 pub const fn height(&self) -> usize {
364 self.0.0.as_deref_except().height as usize
365 }
366
367 pub fn sample_aspect_ratio(&self) -> Rational {
369 self.0.0.as_deref_except().sample_aspect_ratio.into()
370 }
371
372 pub fn set_sample_aspect_ratio(&mut self, sample_aspect_ratio: impl Into<Rational>) {
374 self.0.0.as_deref_mut_except().sample_aspect_ratio = sample_aspect_ratio.into().into();
375 }
376
377 pub const fn is_keyframe(&self) -> bool {
379 self.0.0.as_deref_except().key_frame != 0
380 }
381
382 pub const fn pict_type(&self) -> AVPictureType {
384 AVPictureType(self.0.0.as_deref_except().pict_type as _)
385 }
386
387 pub const fn set_pict_type(&mut self, pict_type: AVPictureType) {
389 self.0.0.as_deref_mut_except().pict_type = pict_type.0 as _;
390 }
391
392 pub fn data(&self, index: usize) -> Option<Const<FrameData, '_>> {
394 let descriptor = unsafe { rusty_ffmpeg::ffi::av_pix_fmt_desc_get(self.format().into()) };
396 let descriptor = unsafe { descriptor.as_ref()? };
398
399 let line = self.linesize(index)?;
400 let height = {
401 if descriptor.flags & rusty_ffmpeg::ffi::AV_PIX_FMT_FLAG_PAL as u64 != 0 && index == 1 {
403 1
404 } else if index > 0 {
405 self.height() >> descriptor.log2_chroma_h
406 } else {
407 self.height()
408 }
409 };
410
411 let raw = NonNull::new(*(self.0.0.as_deref_except().data.get(index)?))?;
412
413 Some(Const::new(FrameData {
414 ptr: raw,
415 linesize: line,
416 height: height as i32,
417 }))
418 }
419
420 pub fn data_mut(&mut self, index: usize) -> Option<Mut<FrameData, '_>> {
422 let descriptor = unsafe { rusty_ffmpeg::ffi::av_pix_fmt_desc_get(self.format().into()) };
424 let descriptor = unsafe { descriptor.as_ref()? };
426
427 let line = self.linesize(index)?;
428 let height = {
429 if descriptor.flags & rusty_ffmpeg::ffi::AV_PIX_FMT_FLAG_PAL as u64 != 0 && index == 1 {
431 1
432 } else if index > 0 {
433 self.height() >> descriptor.log2_chroma_h
434 } else {
435 self.height()
436 }
437 };
438
439 let raw = NonNull::new(*(self.0.0.as_deref_except().data.get(index)?))?;
440
441 Some(Mut::new(FrameData {
442 ptr: raw,
443 linesize: line,
444 height: height as i32,
445 }))
446 }
447
448 pub const fn format(&self) -> AVPixelFormat {
450 AVPixelFormat(self.0.0.as_deref_except().format)
451 }
452}
453
454impl std::fmt::Debug for VideoFrame {
455 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456 f.debug_struct("VideoFrame")
457 .field("width", &self.width())
458 .field("height", &self.height())
459 .field("sample_aspect_ratio", &self.sample_aspect_ratio())
460 .field("pts", &self.pts())
461 .field("dts", &self.dts())
462 .field("duration", &self.duration())
463 .field("best_effort_timestamp", &self.best_effort_timestamp())
464 .field("time_base", &self.time_base())
465 .field("format", &self.format())
466 .field("is_audio", &self.is_audio())
467 .field("is_video", &self.is_video())
468 .field("is_keyframe", &self.is_keyframe())
469 .finish()
470 }
471}
472
473impl std::ops::Deref for VideoFrame {
474 type Target = GenericFrame;
475
476 fn deref(&self) -> &Self::Target {
477 &self.0
478 }
479}
480
481impl std::ops::DerefMut for VideoFrame {
482 fn deref_mut(&mut self) -> &mut Self::Target {
483 &mut self.0
484 }
485}
486
487pub struct AudioChannelLayout(SmartObject<AVChannelLayout>);
489
490impl Default for AudioChannelLayout {
491 fn default() -> Self {
492 let zeroed_layout = unsafe { std::mem::zeroed() };
494
495 Self(SmartObject::new(zeroed_layout, Self::destructor))
496 }
497}
498
499impl AudioChannelLayout {
500 #[doc(hidden)]
501 fn destructor(ptr: &mut AVChannelLayout) {
502 unsafe { av_channel_layout_uninit(ptr) };
504 }
505
506 pub fn new(channels: i32) -> Result<Self, FfmpegError> {
508 let mut layout = Self::default();
509
510 unsafe { av_channel_layout_default(layout.0.as_mut(), channels) };
512
513 layout.validate()?;
514
515 Ok(layout)
516 }
517
518 pub fn copy(&self) -> Result<Self, FfmpegError> {
520 let mut new = Self::default();
521 FfmpegErrorCode(unsafe { av_channel_layout_copy(new.0.inner_mut(), self.0.inner_ref()) }).result()?;
523 Ok(new)
524 }
525
526 pub(crate) fn as_ptr(&self) -> *const AVChannelLayout {
528 self.0.as_ref()
529 }
530
531 pub fn validate(&self) -> Result<(), FfmpegError> {
533 if unsafe { av_channel_layout_check(self.0.as_ref()) } == 0 {
535 return Err(FfmpegError::Arguments("invalid channel layout"));
536 }
537
538 Ok(())
539 }
540
541 pub unsafe fn wrap(layout: AVChannelLayout) -> Self {
546 Self(SmartObject::new(layout, Self::destructor))
547 }
548
549 pub fn channel_count(&self) -> i32 {
551 self.0.as_ref().nb_channels
552 }
553
554 pub fn into_inner(self) -> AVChannelLayout {
557 self.0.into_inner()
558 }
559
560 pub(crate) fn apply(mut self, layout: &mut AVChannelLayout) {
561 std::mem::swap(layout, self.0.as_mut());
562 }
563}
564
565#[bon::bon]
566impl AudioFrame {
567 #[builder]
569 pub fn new(
570 channel_layout: AudioChannelLayout,
571 nb_samples: i32,
572 sample_fmt: AVSampleFormat,
573 sample_rate: i32,
574 #[builder(default = 0)] duration: i64,
575 #[builder(default = AV_NOPTS_VALUE)] pts: i64,
576 #[builder(default = AV_NOPTS_VALUE)] dts: i64,
577 #[builder(default = Rational::ZERO)] time_base: Rational,
578 #[builder(default = 0)]
580 alignment: i32,
581 ) -> Result<Self, FfmpegError> {
582 if sample_rate <= 0 || nb_samples <= 0 {
583 return Err(FfmpegError::Arguments(
584 "sample_rate and nb_samples must be positive and not 0",
585 ));
586 }
587 if alignment < 0 {
588 return Err(FfmpegError::Arguments("alignment must be positive"));
589 }
590
591 let mut generic = GenericFrame::new()?;
592 let inner = generic.0.as_deref_mut_except();
593
594 channel_layout.apply(&mut inner.ch_layout);
595 inner.nb_samples = nb_samples;
596 inner.format = sample_fmt.into();
597 inner.sample_rate = sample_rate;
598 inner.duration = duration;
599 inner.pts = pts;
600 inner.best_effort_timestamp = pts;
601 inner.time_base = time_base.into();
602 inner.pkt_dts = dts;
603
604 unsafe { generic.alloc_frame_buffer(Some(alignment))? };
606
607 Ok(Self(generic))
608 }
609
610 pub const fn channel_layout(&self) -> AVChannelLayout {
612 self.0.0.as_deref_except().ch_layout
613 }
614
615 pub const fn channel_count(&self) -> usize {
617 self.0.0.as_deref_except().ch_layout.nb_channels as usize
618 }
619
620 pub const fn nb_samples(&self) -> i32 {
622 self.0.0.as_deref_except().nb_samples
623 }
624
625 pub const fn sample_rate(&self) -> i32 {
627 self.0.0.as_deref_except().sample_rate
628 }
629
630 pub const fn set_sample_rate(&mut self, sample_rate: usize) {
632 self.0.0.as_deref_mut_except().sample_rate = sample_rate as i32;
633 }
634
635 pub fn data(&self, index: usize) -> Option<&[u8]> {
637 let ptr = *self.0.0.as_deref_except().data.get(index)?;
638
639 if ptr.is_null() {
640 return None;
641 }
642
643 let linesize = self.linesize(index)?;
645
646 if linesize.is_negative() {
647 return None;
648 }
649
650 Some(unsafe { core::slice::from_raw_parts(ptr, linesize as usize) })
652 }
653
654 pub fn data_mut(&mut self, index: usize) -> Option<&mut [u8]> {
656 let ptr = *self.0.0.as_deref_except().data.get(index)?;
657
658 if ptr.is_null() {
659 return None;
660 }
661
662 let linesize = self.linesize(index)?;
664
665 if linesize.is_negative() {
666 return None;
667 }
668
669 Some(unsafe { core::slice::from_raw_parts_mut(ptr, linesize as usize) })
671 }
672}
673
674impl std::fmt::Debug for AudioFrame {
675 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
676 f.debug_struct("AudioFrame")
677 .field("channel_count", &self.channel_count())
678 .field("nb_samples", &self.nb_samples())
679 .field("sample_rate", &self.sample_rate())
680 .field("pts", &self.pts())
681 .field("dts", &self.dts())
682 .field("duration", &self.duration())
683 .field("best_effort_timestamp", &self.best_effort_timestamp())
684 .field("time_base", &self.time_base())
685 .field("format", &self.format())
686 .field("is_audio", &self.is_audio())
687 .field("is_video", &self.is_video())
688 .finish()
689 }
690}
691
692impl std::ops::Deref for AudioFrame {
693 type Target = GenericFrame;
694
695 fn deref(&self) -> &Self::Target {
696 &self.0
697 }
698}
699
700impl std::ops::DerefMut for AudioFrame {
701 fn deref_mut(&mut self) -> &mut Self::Target {
702 &mut self.0
703 }
704}
705
706#[cfg(test)]
707#[cfg_attr(all(test, coverage_nightly), coverage(off))]
708mod tests {
709 use insta::assert_debug_snapshot;
710 use rand::{Rng, rng};
711
712 use super::FrameData;
713 use crate::frame::{AudioChannelLayout, AudioFrame, GenericFrame, VideoFrame};
714 use crate::rational::Rational;
715 use crate::{AVChannelOrder, AVPictureType, AVPixelFormat, AVSampleFormat};
716
717 #[test]
718 fn test_frame_clone() {
719 let frame = VideoFrame::builder()
720 .width(16)
721 .height(16)
722 .pts(12)
723 .dts(34)
724 .duration(5)
725 .time_base(Rational::static_new::<1, 30>())
726 .pix_fmt(AVPixelFormat::Yuv420p)
727 .build()
728 .expect("failed to build VideoFrame");
729
730 let cloned_frame = frame.clone();
731
732 assert_eq!(
733 format!("{frame:?}"),
734 format!("{:?}", cloned_frame),
735 "Cloned frame should be equal to the original frame."
736 );
737 }
738
739 #[test]
740 fn test_audio_conversion() {
741 let mut frame = GenericFrame::new().expect("Failed to create frame");
742 AudioChannelLayout::new(2)
743 .unwrap()
744 .apply(&mut frame.0.as_deref_mut_except().ch_layout);
745 let audio_frame = frame.audio();
746
747 assert!(audio_frame.is_audio(), "The frame should be identified as audio.");
748 assert!(!audio_frame.is_video(), "The frame should not be identified as video.");
749 }
750
751 #[test]
752 fn test_linesize() {
753 let frame = VideoFrame::builder()
754 .width(1920)
755 .height(1080)
756 .pix_fmt(AVPixelFormat::Yuv420p)
757 .build()
758 .expect("Failed to create frame");
759
760 assert!(
761 frame.linesize(0).unwrap_or(0) > 0,
762 "Linesize should be greater than zero for valid index."
763 );
764
765 assert!(
766 frame.linesize(100).is_none(),
767 "Linesize at an invalid index should return None."
768 );
769 }
770
771 #[test]
772 fn test_frame_debug() {
773 let mut frame = GenericFrame::new().expect("Failed to create frame");
774 frame.set_pts(Some(12345));
775 frame.set_dts(Some(67890));
776 frame.set_duration(Some(1000));
777 frame.set_time_base(Rational::static_new::<1, 30>());
778 frame.0.as_deref_mut_except().format = AVPixelFormat::Yuv420p.into();
779
780 assert_debug_snapshot!(frame, @r"
781 GenericFrame {
782 pts: Some(
783 12345,
784 ),
785 dts: Some(
786 67890,
787 ),
788 duration: Some(
789 1000,
790 ),
791 best_effort_timestamp: Some(
792 12345,
793 ),
794 time_base: Rational {
795 numerator: 1,
796 denominator: 30,
797 },
798 format: 0,
799 is_audio: false,
800 is_video: false,
801 }
802 ");
803 }
804
805 #[test]
806 fn test_sample_aspect_ratio() {
807 let frame = GenericFrame::new().expect("Failed to create frame");
808 let mut video_frame = frame.video();
809 let sample_aspect_ratio = Rational::static_new::<16, 9>();
810 video_frame.set_sample_aspect_ratio(sample_aspect_ratio);
811
812 assert_eq!(
813 video_frame.sample_aspect_ratio(),
814 sample_aspect_ratio,
815 "Sample aspect ratio should match the set value."
816 );
817 }
818
819 #[test]
820 fn test_pict_type() {
821 let frame = GenericFrame::new().expect("Failed to create frame");
822 let mut video_frame = frame.video();
823 video_frame.set_pict_type(AVPictureType::Intra);
824
825 assert_eq!(
826 video_frame.pict_type(),
827 AVPictureType::Intra,
828 "Picture type should match the set value."
829 );
830 }
831
832 #[test]
833 fn test_data_allocation_and_access() {
834 let mut video_frame = VideoFrame::builder()
835 .width(16)
836 .height(16)
837 .pix_fmt(AVPixelFormat::Yuv420p)
838 .alignment(32)
839 .build()
840 .expect("Failed to create VideoFrame");
841
842 let mut randomized_data: Vec<Vec<u8>> = Vec::with_capacity(video_frame.height());
843
844 if let Some(mut data) = video_frame.data_mut(0) {
845 for row in 0..data.height() {
846 let data_slice = data.get_row_mut(row as usize).unwrap();
847 randomized_data.push(
848 (0..data_slice.len())
849 .map(|_| rng().random::<u8>()) .collect(),
851 );
852 data_slice.copy_from_slice(&randomized_data[row as usize]); }
854 } else {
855 panic!("Failed to get valid data buffer for Y-plane.");
856 }
857
858 if let Some(data) = video_frame.data(0) {
859 for row in 0..data.height() {
860 let data_slice = data.get_row(row as usize).unwrap();
861 assert_eq!(
862 data_slice,
863 randomized_data[row as usize].as_slice(),
864 "Data does not match randomized content."
865 );
866 }
867 } else {
868 panic!("Data at index 0 should not be None.");
869 }
870 }
871
872 #[test]
873 fn test_video_frame_debug() {
874 let video_frame = VideoFrame::builder()
875 .pts(12345)
876 .dts(67890)
877 .duration(1000)
878 .time_base(Rational::static_new::<1, 30>())
879 .pix_fmt(AVPixelFormat::Yuv420p)
880 .width(1920)
881 .height(1080)
882 .sample_aspect_ratio(Rational::static_new::<16, 9>())
883 .build()
884 .expect("Failed to create a new VideoFrame");
885
886 assert_debug_snapshot!(video_frame, @r"
887 VideoFrame {
888 width: 1920,
889 height: 1080,
890 sample_aspect_ratio: Rational {
891 numerator: 16,
892 denominator: 9,
893 },
894 pts: Some(
895 12345,
896 ),
897 dts: Some(
898 67890,
899 ),
900 duration: Some(
901 1000,
902 ),
903 best_effort_timestamp: Some(
904 12345,
905 ),
906 time_base: Rational {
907 numerator: 1,
908 denominator: 30,
909 },
910 format: AVPixelFormat::Yuv420p,
911 is_audio: false,
912 is_video: true,
913 is_keyframe: false,
914 }
915 ");
916 }
917
918 #[test]
919 fn test_set_channel_layout_custom_invalid_layout_error() {
920 let custom_layout = unsafe {
922 AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
923 order: AVChannelOrder::Native.into(),
924 nb_channels: -1,
925 u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 2 },
926 opaque: std::ptr::null_mut(),
927 })
928 };
929 let audio_frame = AudioFrame::builder()
930 .channel_layout(custom_layout)
931 .nb_samples(123)
932 .sample_fmt(AVSampleFormat::S16)
933 .sample_rate(44100)
934 .build();
935
936 assert!(audio_frame.is_err(), "Expected error for invalid custom channel layout");
937 }
938
939 #[test]
940 fn test_set_channel_layout_custom() {
941 let custom_layout = unsafe {
943 AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
944 order: AVChannelOrder::Native.into(),
945 nb_channels: 2,
946 u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 3 },
947 opaque: std::ptr::null_mut(),
948 })
949 };
950
951 let audio_frame = AudioFrame::builder()
952 .channel_layout(custom_layout)
953 .nb_samples(123)
954 .sample_fmt(AVSampleFormat::S16)
955 .sample_rate(44100)
956 .build()
957 .expect("Failed to create AudioFrame with custom layout");
958
959 let layout = audio_frame.channel_layout();
960 assert_eq!(layout.nb_channels, 2, "Expected channel layout to have 2 channels (stereo).");
961 assert_eq!(
962 unsafe { layout.u.mask },
964 3,
965 "Expected channel mask to match AV_CH_LAYOUT_STEREO."
966 );
967 assert_eq!(
968 AVChannelOrder(layout.order as _),
969 AVChannelOrder::Native,
970 "Expected channel order to be AV_CHANNEL_ORDER_NATIVE."
971 );
972 }
973
974 #[test]
975 fn test_alloc_frame_buffer() {
976 let cases = [(0, true), (3, true), (32, true), (-1, false)];
977
978 for alignment in cases {
979 let frame = AudioFrame::builder()
980 .sample_fmt(AVSampleFormat::S16)
981 .nb_samples(1024)
982 .channel_layout(AudioChannelLayout::new(1).expect("failed to create a new AudioChannelLayout"))
983 .alignment(alignment.0)
984 .sample_rate(44100)
985 .build();
986
987 assert_eq!(frame.is_ok(), alignment.1)
988 }
989 }
990
991 #[test]
992 fn test_alloc_frame_buffer_error() {
993 let cases = [None, Some(0), Some(32), Some(-1)];
994
995 for alignment in cases {
996 let mut frame = GenericFrame::new().expect("Failed to create frame");
997 frame.0.as_deref_mut_except().format = AVSampleFormat::S16.into();
999 frame.0.as_deref_mut_except().nb_samples = 1024;
1000
1001 assert!(
1002 unsafe { frame.alloc_frame_buffer(alignment).is_err() },
1004 "Should fail to allocate buffer with invalid frame and alignment {alignment:?}"
1005 );
1006 }
1007 }
1008
1009 #[test]
1010 fn test_sample_rate() {
1011 let mut audio_frame = AudioFrame::builder()
1012 .channel_layout(AudioChannelLayout::new(2).expect("Failed to create a new AudioChannelLayout"))
1013 .nb_samples(123)
1014 .sample_fmt(AVSampleFormat::S16)
1015 .sample_rate(44100)
1016 .build()
1017 .expect("Failed to create AudioFrame with custom layout");
1018
1019 audio_frame.set_sample_rate(48000);
1020
1021 assert_eq!(
1022 audio_frame.sample_rate(),
1023 48000,
1024 "The sample rate should match the set value."
1025 );
1026 }
1027
1028 #[test]
1029 fn test_audio_frame_debug() {
1030 let audio_frame = AudioFrame::builder()
1031 .sample_fmt(AVSampleFormat::S16)
1032 .channel_layout(AudioChannelLayout::new(2).expect("failed to create a new AudioChannelLayout"))
1033 .nb_samples(1024)
1034 .sample_rate(44100)
1035 .pts(12345)
1036 .dts(67890)
1037 .duration(512)
1038 .time_base(Rational::static_new::<1, 44100>())
1039 .build()
1040 .expect("failed to create a new AudioFrame");
1041
1042 assert_debug_snapshot!(audio_frame, @r"
1043 AudioFrame {
1044 channel_count: 2,
1045 nb_samples: 1024,
1046 sample_rate: 44100,
1047 pts: Some(
1048 12345,
1049 ),
1050 dts: Some(
1051 67890,
1052 ),
1053 duration: Some(
1054 512,
1055 ),
1056 best_effort_timestamp: Some(
1057 12345,
1058 ),
1059 time_base: Rational {
1060 numerator: 1,
1061 denominator: 44100,
1062 },
1063 format: 1,
1064 is_audio: true,
1065 is_video: false,
1066 }
1067 ");
1068 }
1069
1070 #[test]
1071 fn frame_data_read() {
1072 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1073
1074 let frame_data = FrameData {
1075 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1076 linesize: 3,
1077 height: 2,
1078 };
1079
1080 assert_eq!(frame_data[0], 1);
1081 assert_eq!(frame_data[5], 6);
1082
1083 assert_eq!(frame_data.get_row(0).unwrap(), [1, 2, 3]);
1084 assert_eq!(frame_data.get_row(1).unwrap(), [4, 5, 6]);
1085 assert!(frame_data.get_row(2).is_none());
1086 }
1087
1088 #[test]
1089 fn frame_data_read_inverse() {
1090 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1091 let linesize: i32 = -3;
1092 let height: i32 = 2;
1093 let end_ptr = unsafe { data.as_mut_ptr().byte_offset(((height - 1) * linesize.abs()) as isize) };
1095
1096 let frame_data = FrameData {
1097 ptr: core::ptr::NonNull::new(end_ptr).unwrap(),
1098 linesize,
1099 height,
1100 };
1101
1102 assert_eq!(frame_data[0], 4);
1103 assert_eq!(frame_data[3], 1);
1104 assert_eq!(frame_data[5], 3);
1105
1106 assert_eq!(frame_data.get_row(0).unwrap(), [4, 5, 6]);
1107 assert_eq!(frame_data.get_row(1).unwrap(), [1, 2, 3]);
1108 assert!(frame_data.get_row(2).is_none());
1109 }
1110
1111 #[test]
1112 fn frame_data_read_out_of_bounds() {
1113 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1114
1115 let linesize: i32 = -3;
1116 let height: i32 = 2;
1117 let end_ptr = unsafe { data.as_mut_ptr().byte_offset(((height - 1) * linesize.abs()) as isize) };
1119
1120 let inverse_frame_data = FrameData {
1121 ptr: core::ptr::NonNull::new(end_ptr).unwrap(),
1122 linesize,
1123 height,
1124 };
1125
1126 let frame_data = FrameData {
1127 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1128 linesize: linesize.abs(),
1129 height,
1130 };
1131
1132 assert!(
1133 std::panic::catch_unwind(|| {
1134 let _ = inverse_frame_data[6];
1135 })
1136 .is_err()
1137 );
1138 assert!(
1139 std::panic::catch_unwind(|| {
1140 let _ = frame_data[6];
1141 })
1142 .is_err()
1143 );
1144 }
1145
1146 #[test]
1147 fn frame_data_write() {
1148 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1149
1150 let mut frame_data = FrameData {
1151 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1152 linesize: 3,
1153 height: 2,
1154 };
1155
1156 for i in 1..frame_data.len() {
1157 frame_data[i] = frame_data[0]
1158 }
1159
1160 for i in 0..frame_data.len() {
1161 assert_eq!(frame_data[i], 1, "all bytes of frame_data should be 0")
1162 }
1163 }
1164}