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 fn channel_layout(&self) -> AudioChannelLayout {
612 unsafe { AudioChannelLayout::wrap(self.0.0.as_deref_except().ch_layout) }
615 }
616
617 pub const fn channel_count(&self) -> usize {
619 self.0.0.as_deref_except().ch_layout.nb_channels as usize
620 }
621
622 pub const fn nb_samples(&self) -> i32 {
624 self.0.0.as_deref_except().nb_samples
625 }
626
627 pub const fn sample_rate(&self) -> i32 {
629 self.0.0.as_deref_except().sample_rate
630 }
631
632 pub const fn set_sample_rate(&mut self, sample_rate: usize) {
634 self.0.0.as_deref_mut_except().sample_rate = sample_rate as i32;
635 }
636
637 pub fn data(&self, index: usize) -> Option<&[u8]> {
639 let ptr = *self.0.0.as_deref_except().data.get(index)?;
640
641 if ptr.is_null() {
642 return None;
643 }
644
645 let linesize = self.linesize(index)?;
647
648 if linesize.is_negative() {
649 return None;
650 }
651
652 Some(unsafe { core::slice::from_raw_parts(ptr, linesize as usize) })
654 }
655
656 pub fn data_mut(&mut self, index: usize) -> Option<&mut [u8]> {
658 let ptr = *self.0.0.as_deref_except().data.get(index)?;
659
660 if ptr.is_null() {
661 return None;
662 }
663
664 let linesize = self.linesize(index)?;
666
667 if linesize.is_negative() {
668 return None;
669 }
670
671 Some(unsafe { core::slice::from_raw_parts_mut(ptr, linesize as usize) })
673 }
674
675 pub const fn format(&self) -> AVSampleFormat {
677 AVSampleFormat(self.0.0.as_deref_except().format)
678 }
679}
680
681impl std::fmt::Debug for AudioFrame {
682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683 f.debug_struct("AudioFrame")
684 .field("channel_count", &self.channel_count())
685 .field("nb_samples", &self.nb_samples())
686 .field("sample_rate", &self.sample_rate())
687 .field("pts", &self.pts())
688 .field("dts", &self.dts())
689 .field("duration", &self.duration())
690 .field("best_effort_timestamp", &self.best_effort_timestamp())
691 .field("time_base", &self.time_base())
692 .field("format", &self.format())
693 .field("is_audio", &self.is_audio())
694 .field("is_video", &self.is_video())
695 .finish()
696 }
697}
698
699impl std::ops::Deref for AudioFrame {
700 type Target = GenericFrame;
701
702 fn deref(&self) -> &Self::Target {
703 &self.0
704 }
705}
706
707impl std::ops::DerefMut for AudioFrame {
708 fn deref_mut(&mut self) -> &mut Self::Target {
709 &mut self.0
710 }
711}
712
713#[cfg(test)]
714#[cfg_attr(all(test, coverage_nightly), coverage(off))]
715mod tests {
716 use insta::assert_debug_snapshot;
717 use rand::{Rng, rng};
718
719 use super::FrameData;
720 use crate::frame::{AudioChannelLayout, AudioFrame, GenericFrame, VideoFrame};
721 use crate::rational::Rational;
722 use crate::{AVChannelOrder, AVPictureType, AVPixelFormat, AVSampleFormat};
723
724 #[test]
725 fn test_frame_clone() {
726 let frame = VideoFrame::builder()
727 .width(16)
728 .height(16)
729 .pts(12)
730 .dts(34)
731 .duration(5)
732 .time_base(Rational::static_new::<1, 30>())
733 .pix_fmt(AVPixelFormat::Yuv420p)
734 .build()
735 .expect("failed to build VideoFrame");
736
737 let cloned_frame = frame.clone();
738
739 assert_eq!(
740 format!("{frame:?}"),
741 format!("{:?}", cloned_frame),
742 "Cloned frame should be equal to the original frame."
743 );
744 }
745
746 #[test]
747 fn test_audio_conversion() {
748 let mut frame = GenericFrame::new().expect("Failed to create frame");
749 AudioChannelLayout::new(2)
750 .unwrap()
751 .apply(&mut frame.0.as_deref_mut_except().ch_layout);
752 let audio_frame = frame.audio();
753
754 assert!(audio_frame.is_audio(), "The frame should be identified as audio.");
755 assert!(!audio_frame.is_video(), "The frame should not be identified as video.");
756 }
757
758 #[test]
759 fn test_linesize() {
760 let frame = VideoFrame::builder()
761 .width(1920)
762 .height(1080)
763 .pix_fmt(AVPixelFormat::Yuv420p)
764 .build()
765 .expect("Failed to create frame");
766
767 assert!(
768 frame.linesize(0).unwrap_or(0) > 0,
769 "Linesize should be greater than zero for valid index."
770 );
771
772 assert!(
773 frame.linesize(100).is_none(),
774 "Linesize at an invalid index should return None."
775 );
776 }
777
778 #[test]
779 fn test_frame_debug() {
780 let mut frame = GenericFrame::new().expect("Failed to create frame");
781 frame.set_pts(Some(12345));
782 frame.set_dts(Some(67890));
783 frame.set_duration(Some(1000));
784 frame.set_time_base(Rational::static_new::<1, 30>());
785 frame.0.as_deref_mut_except().format = AVPixelFormat::Yuv420p.into();
786
787 assert_debug_snapshot!(frame, @r"
788 GenericFrame {
789 pts: Some(
790 12345,
791 ),
792 dts: Some(
793 67890,
794 ),
795 duration: Some(
796 1000,
797 ),
798 best_effort_timestamp: Some(
799 12345,
800 ),
801 time_base: Rational {
802 numerator: 1,
803 denominator: 30,
804 },
805 format: 0,
806 is_audio: false,
807 is_video: false,
808 }
809 ");
810 }
811
812 #[test]
813 fn test_sample_aspect_ratio() {
814 let frame = GenericFrame::new().expect("Failed to create frame");
815 let mut video_frame = frame.video();
816 let sample_aspect_ratio = Rational::static_new::<16, 9>();
817 video_frame.set_sample_aspect_ratio(sample_aspect_ratio);
818
819 assert_eq!(
820 video_frame.sample_aspect_ratio(),
821 sample_aspect_ratio,
822 "Sample aspect ratio should match the set value."
823 );
824 }
825
826 #[test]
827 fn test_pict_type() {
828 let frame = GenericFrame::new().expect("Failed to create frame");
829 let mut video_frame = frame.video();
830 video_frame.set_pict_type(AVPictureType::Intra);
831
832 assert_eq!(
833 video_frame.pict_type(),
834 AVPictureType::Intra,
835 "Picture type should match the set value."
836 );
837 }
838
839 #[test]
840 fn test_data_allocation_and_access() {
841 let mut video_frame = VideoFrame::builder()
842 .width(16)
843 .height(16)
844 .pix_fmt(AVPixelFormat::Yuv420p)
845 .alignment(32)
846 .build()
847 .expect("Failed to create VideoFrame");
848
849 let mut randomized_data: Vec<Vec<u8>> = Vec::with_capacity(video_frame.height());
850
851 if let Some(mut data) = video_frame.data_mut(0) {
852 for row in 0..data.height() {
853 let data_slice = data.get_row_mut(row as usize).unwrap();
854 randomized_data.push(
855 (0..data_slice.len())
856 .map(|_| rng().random::<u8>()) .collect(),
858 );
859 data_slice.copy_from_slice(&randomized_data[row as usize]); }
861 } else {
862 panic!("Failed to get valid data buffer for Y-plane.");
863 }
864
865 if let Some(data) = video_frame.data(0) {
866 for row in 0..data.height() {
867 let data_slice = data.get_row(row as usize).unwrap();
868 assert_eq!(
869 data_slice,
870 randomized_data[row as usize].as_slice(),
871 "Data does not match randomized content."
872 );
873 }
874 } else {
875 panic!("Data at index 0 should not be None.");
876 }
877 }
878
879 #[test]
880 fn test_video_frame_debug() {
881 let video_frame = VideoFrame::builder()
882 .pts(12345)
883 .dts(67890)
884 .duration(1000)
885 .time_base(Rational::static_new::<1, 30>())
886 .pix_fmt(AVPixelFormat::Yuv420p)
887 .width(1920)
888 .height(1080)
889 .sample_aspect_ratio(Rational::static_new::<16, 9>())
890 .build()
891 .expect("Failed to create a new VideoFrame");
892
893 assert_debug_snapshot!(video_frame, @r"
894 VideoFrame {
895 width: 1920,
896 height: 1080,
897 sample_aspect_ratio: Rational {
898 numerator: 16,
899 denominator: 9,
900 },
901 pts: Some(
902 12345,
903 ),
904 dts: Some(
905 67890,
906 ),
907 duration: Some(
908 1000,
909 ),
910 best_effort_timestamp: Some(
911 12345,
912 ),
913 time_base: Rational {
914 numerator: 1,
915 denominator: 30,
916 },
917 format: AVPixelFormat::Yuv420p,
918 is_audio: false,
919 is_video: true,
920 is_keyframe: false,
921 }
922 ");
923 }
924
925 #[test]
926 fn test_set_channel_layout_custom_invalid_layout_error() {
927 let custom_layout = unsafe {
929 AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
930 order: AVChannelOrder::Native.into(),
931 nb_channels: -1,
932 u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 2 },
933 opaque: std::ptr::null_mut(),
934 })
935 };
936 let audio_frame = AudioFrame::builder()
937 .channel_layout(custom_layout)
938 .nb_samples(123)
939 .sample_fmt(AVSampleFormat::S16)
940 .sample_rate(44100)
941 .build();
942
943 assert!(audio_frame.is_err(), "Expected error for invalid custom channel layout");
944 }
945
946 #[test]
947 fn test_set_channel_layout_custom() {
948 let custom_layout = unsafe {
950 AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
951 order: AVChannelOrder::Native.into(),
952 nb_channels: 2,
953 u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 3 },
954 opaque: std::ptr::null_mut(),
955 })
956 };
957
958 let audio_frame = AudioFrame::builder()
959 .channel_layout(custom_layout)
960 .nb_samples(123)
961 .sample_fmt(AVSampleFormat::S16)
962 .sample_rate(44100)
963 .build()
964 .expect("Failed to create AudioFrame with custom layout");
965
966 let layout = audio_frame.channel_layout();
967 assert_eq!(
968 layout.channel_count(),
969 2,
970 "Expected channel layout to have 2 channels (stereo)."
971 );
972 assert_eq!(
973 unsafe { layout.0.u.mask },
975 3,
976 "Expected channel mask to match AV_CH_LAYOUT_STEREO."
977 );
978 assert_eq!(
979 AVChannelOrder(layout.0.order as _),
980 AVChannelOrder::Native,
981 "Expected channel order to be AV_CHANNEL_ORDER_NATIVE."
982 );
983 }
984
985 #[test]
986 fn test_alloc_frame_buffer() {
987 let cases = [(0, true), (3, true), (32, true), (-1, false)];
988
989 for alignment in cases {
990 let frame = AudioFrame::builder()
991 .sample_fmt(AVSampleFormat::S16)
992 .nb_samples(1024)
993 .channel_layout(AudioChannelLayout::new(1).expect("failed to create a new AudioChannelLayout"))
994 .alignment(alignment.0)
995 .sample_rate(44100)
996 .build();
997
998 assert_eq!(frame.is_ok(), alignment.1)
999 }
1000 }
1001
1002 #[test]
1003 fn test_alloc_frame_buffer_error() {
1004 let cases = [None, Some(0), Some(32), Some(-1)];
1005
1006 for alignment in cases {
1007 let mut frame = GenericFrame::new().expect("Failed to create frame");
1008 frame.0.as_deref_mut_except().format = AVSampleFormat::S16.into();
1010 frame.0.as_deref_mut_except().nb_samples = 1024;
1011
1012 assert!(
1013 unsafe { frame.alloc_frame_buffer(alignment).is_err() },
1015 "Should fail to allocate buffer with invalid frame and alignment {alignment:?}"
1016 );
1017 }
1018 }
1019
1020 #[test]
1021 fn test_sample_rate() {
1022 let mut audio_frame = AudioFrame::builder()
1023 .channel_layout(AudioChannelLayout::new(2).expect("Failed to create a new AudioChannelLayout"))
1024 .nb_samples(123)
1025 .sample_fmt(AVSampleFormat::S16)
1026 .sample_rate(44100)
1027 .build()
1028 .expect("Failed to create AudioFrame with custom layout");
1029
1030 audio_frame.set_sample_rate(48000);
1031
1032 assert_eq!(
1033 audio_frame.sample_rate(),
1034 48000,
1035 "The sample rate should match the set value."
1036 );
1037 }
1038
1039 #[test]
1040 fn test_audio_frame_debug() {
1041 let audio_frame = AudioFrame::builder()
1042 .sample_fmt(AVSampleFormat::S16)
1043 .channel_layout(AudioChannelLayout::new(2).expect("failed to create a new AudioChannelLayout"))
1044 .nb_samples(1024)
1045 .sample_rate(44100)
1046 .pts(12345)
1047 .dts(67890)
1048 .duration(512)
1049 .time_base(Rational::static_new::<1, 44100>())
1050 .build()
1051 .expect("failed to create a new AudioFrame");
1052
1053 assert_debug_snapshot!(audio_frame, @r"
1054 AudioFrame {
1055 channel_count: 2,
1056 nb_samples: 1024,
1057 sample_rate: 44100,
1058 pts: Some(
1059 12345,
1060 ),
1061 dts: Some(
1062 67890,
1063 ),
1064 duration: Some(
1065 512,
1066 ),
1067 best_effort_timestamp: Some(
1068 12345,
1069 ),
1070 time_base: Rational {
1071 numerator: 1,
1072 denominator: 44100,
1073 },
1074 format: AVSampleFormat::S16,
1075 is_audio: true,
1076 is_video: false,
1077 }
1078 ");
1079 }
1080
1081 #[test]
1082 fn frame_data_read() {
1083 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1084
1085 let frame_data = FrameData {
1086 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1087 linesize: 3,
1088 height: 2,
1089 };
1090
1091 assert_eq!(frame_data[0], 1);
1092 assert_eq!(frame_data[5], 6);
1093
1094 assert_eq!(frame_data.get_row(0).unwrap(), [1, 2, 3]);
1095 assert_eq!(frame_data.get_row(1).unwrap(), [4, 5, 6]);
1096 assert!(frame_data.get_row(2).is_none());
1097 }
1098
1099 #[test]
1100 fn frame_data_read_inverse() {
1101 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1102 let linesize: i32 = -3;
1103 let height: i32 = 2;
1104 let end_ptr = unsafe { data.as_mut_ptr().byte_offset(((height - 1) * linesize.abs()) as isize) };
1106
1107 let frame_data = FrameData {
1108 ptr: core::ptr::NonNull::new(end_ptr).unwrap(),
1109 linesize,
1110 height,
1111 };
1112
1113 assert_eq!(frame_data[0], 4);
1114 assert_eq!(frame_data[3], 1);
1115 assert_eq!(frame_data[5], 3);
1116
1117 assert_eq!(frame_data.get_row(0).unwrap(), [4, 5, 6]);
1118 assert_eq!(frame_data.get_row(1).unwrap(), [1, 2, 3]);
1119 assert!(frame_data.get_row(2).is_none());
1120 }
1121
1122 #[test]
1123 fn frame_data_read_out_of_bounds() {
1124 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1125
1126 let linesize: i32 = -3;
1127 let height: i32 = 2;
1128 let end_ptr = unsafe { data.as_mut_ptr().byte_offset(((height - 1) * linesize.abs()) as isize) };
1130
1131 let inverse_frame_data = FrameData {
1132 ptr: core::ptr::NonNull::new(end_ptr).unwrap(),
1133 linesize,
1134 height,
1135 };
1136
1137 let frame_data = FrameData {
1138 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1139 linesize: linesize.abs(),
1140 height,
1141 };
1142
1143 assert!(
1144 std::panic::catch_unwind(|| {
1145 let _ = inverse_frame_data[6];
1146 })
1147 .is_err()
1148 );
1149 assert!(
1150 std::panic::catch_unwind(|| {
1151 let _ = frame_data[6];
1152 })
1153 .is_err()
1154 );
1155 }
1156
1157 #[test]
1158 fn frame_data_write() {
1159 let data: &mut [u8] = &mut [1, 2, 3, 4, 5, 6];
1160
1161 let mut frame_data = FrameData {
1162 ptr: core::ptr::NonNull::new(data.as_mut_ptr()).unwrap(),
1163 linesize: 3,
1164 height: 2,
1165 };
1166
1167 for i in 1..frame_data.len() {
1168 frame_data[i] = frame_data[0]
1169 }
1170
1171 for i in 0..frame_data.len() {
1172 assert_eq!(frame_data[i], 1, "all bytes of frame_data should be 0")
1173 }
1174 }
1175}