scuffle_ffmpeg/
frame.rs

1use crate::error::{FfmpegError, FfmpegErrorCode};
2use crate::ffi::*;
3use crate::rational::Rational;
4use crate::smart_object::{SmartObject, SmartPtr};
5use crate::utils::{check_i64, or_nopts};
6use crate::{AVPictureType, AVPixelFormat};
7
8/// A frame. Thin wrapper around [`AVFrame`].
9pub struct GenericFrame(SmartPtr<AVFrame>);
10
11impl Clone for GenericFrame {
12    fn clone(&self) -> Self {
13        // Safety: `av_frame_clone` is safe to call.
14        let clone = unsafe { av_frame_clone(self.0.as_ptr()) };
15
16        // Safety: The pointer here is valid.
17        unsafe { Self::wrap(clone).expect("failed to clone frame") }
18    }
19}
20
21/// Safety: `GenericFrame` is safe to send between threads.
22unsafe impl Send for GenericFrame {}
23
24/// Safety: `GenericFrame` is safe to share between threads.
25unsafe impl Sync for GenericFrame {}
26
27/// A video frame. Thin wrapper around [`GenericFrame`]. Like a frame but has specific video properties.
28#[derive(Clone)]
29pub struct VideoFrame(GenericFrame);
30
31/// An audio frame. Thin wrapper around [`GenericFrame`]. Like a frame but has specific audio properties.
32#[derive(Clone)]
33pub struct AudioFrame(GenericFrame);
34
35impl GenericFrame {
36    /// Creates a new frame.
37    pub(crate) fn new() -> Result<Self, FfmpegError> {
38        // Safety: `av_frame_alloc` is safe to call.
39        let frame = unsafe { av_frame_alloc() };
40
41        // Safety: The pointer here is valid.
42        unsafe { Self::wrap(frame).ok_or(FfmpegError::Alloc) }
43    }
44
45    /// Wraps a pointer to an `AVFrame`.
46    ///
47    /// # Safety
48    /// `ptr` must be a valid pointer to an `AVFrame`.
49    pub(crate) unsafe fn wrap(ptr: *mut AVFrame) -> Option<Self> {
50        SmartPtr::wrap_non_null(ptr, |ptr| av_frame_free(ptr)).map(Self)
51    }
52
53    /// Allocates a buffer for the frame.
54    ///
55    /// # Safety
56    /// This function is unsafe because the caller must ensure the frame has not been allocated yet.
57    /// Also the frame must be properly initialized after the allocation as the data is not zeroed out.
58    /// Therefore reading from the frame after allocation will result in reading uninitialized data.
59    pub(crate) unsafe fn alloc_frame_buffer(&mut self, alignment: Option<i32>) -> Result<(), FfmpegError> {
60        // Safety: `self.as_mut_ptr()` is assumed to provide a valid mutable pointer to an
61        // `AVFrame` structure. The `av_frame_get_buffer` function from FFMPEG allocates
62        // and attaches a buffer to the `AVFrame` if it doesn't already exist.
63        // It is the caller's responsibility to ensure that `self` is properly initialized
64        // and represents a valid `AVFrame` instance.
65        FfmpegErrorCode(unsafe { av_frame_get_buffer(self.as_mut_ptr(), alignment.unwrap_or(0)) }).result()?;
66        Ok(())
67    }
68
69    /// Returns a pointer to the frame.
70    pub(crate) const fn as_ptr(&self) -> *const AVFrame {
71        self.0.as_ptr()
72    }
73
74    /// Returns a mutable pointer to the frame.
75    pub(crate) const fn as_mut_ptr(&mut self) -> *mut AVFrame {
76        self.0.as_mut_ptr()
77    }
78
79    /// Make this frame a video frame.
80    pub(crate) const fn video(self) -> VideoFrame {
81        VideoFrame(self)
82    }
83
84    /// Make this frame an audio frame.
85    pub(crate) const fn audio(self) -> AudioFrame {
86        AudioFrame(self)
87    }
88
89    /// Returns the presentation timestamp of the frame.
90    pub(crate) const fn pts(&self) -> Option<i64> {
91        check_i64(self.0.as_deref_except().pts)
92    }
93
94    /// Sets the presentation timestamp of the frame.
95    pub(crate) const fn set_pts(&mut self, pts: Option<i64>) {
96        self.0.as_deref_mut_except().pts = or_nopts(pts);
97        self.0.as_deref_mut_except().best_effort_timestamp = or_nopts(pts);
98    }
99
100    /// Returns the duration of the frame.
101    pub(crate) const fn duration(&self) -> Option<i64> {
102        check_i64(self.0.as_deref_except().duration)
103    }
104
105    /// Sets the duration of the frame.
106    pub(crate) const fn set_duration(&mut self, duration: Option<i64>) {
107        self.0.as_deref_mut_except().duration = or_nopts(duration);
108    }
109
110    /// Returns the best effort timestamp of the frame.
111    pub(crate) const fn best_effort_timestamp(&self) -> Option<i64> {
112        check_i64(self.0.as_deref_except().best_effort_timestamp)
113    }
114
115    /// Returns the decoding timestamp of the frame.
116    pub(crate) const fn dts(&self) -> Option<i64> {
117        check_i64(self.0.as_deref_except().pkt_dts)
118    }
119
120    /// Sets the decoding timestamp of the frame.
121    pub(crate) const fn set_dts(&mut self, dts: Option<i64>) {
122        self.0.as_deref_mut_except().pkt_dts = or_nopts(dts);
123    }
124
125    /// Returns the time base of the frame.
126    pub(crate) fn time_base(&self) -> Rational {
127        self.0.as_deref_except().time_base.into()
128    }
129
130    /// Sets the time base of the frame.
131    pub(crate) fn set_time_base(&mut self, time_base: impl Into<Rational>) {
132        self.0.as_deref_mut_except().time_base = time_base.into().into();
133    }
134
135    /// Returns the format of the frame.
136    pub(crate) const fn format(&self) -> i32 {
137        self.0.as_deref_except().format
138    }
139
140    /// Sets the format of the frame.
141    pub(crate) const fn set_format(&mut self, format: i32) {
142        self.0.as_deref_mut_except().format = format;
143    }
144
145    /// Returns true if the frame is an audio frame.
146    pub(crate) const fn is_audio(&self) -> bool {
147        self.0.as_deref_except().ch_layout.nb_channels != 0
148    }
149
150    /// Returns true if the frame is a video frame.
151    pub(crate) const fn is_video(&self) -> bool {
152        self.0.as_deref_except().width != 0
153    }
154
155    /// Returns the linesize of the frame.
156    pub(crate) const fn linesize(&self, index: usize) -> Option<i32> {
157        if index >= self.0.as_deref_except().linesize.len() {
158            return None;
159        }
160        Some(self.0.as_deref_except().linesize[index])
161    }
162}
163
164impl std::fmt::Debug for GenericFrame {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_struct("GenericFrame")
167            .field("pts", &self.pts())
168            .field("dts", &self.dts())
169            .field("duration", &self.duration())
170            .field("best_effort_timestamp", &self.best_effort_timestamp())
171            .field("time_base", &self.time_base())
172            .field("format", &self.format())
173            .field("is_audio", &self.is_audio())
174            .field("is_video", &self.is_video())
175            .finish()
176    }
177}
178
179impl VideoFrame {
180    /// Creates a new video frame.
181    pub fn new() -> Result<Self, FfmpegError> {
182        let frame = GenericFrame::new()?;
183        Ok(VideoFrame(frame))
184    }
185
186    /// Returns the width of the frame.
187    pub const fn width(&self) -> usize {
188        self.0 .0.as_deref_except().width as usize
189    }
190
191    /// Returns the height of the frame.
192    pub const fn height(&self) -> usize {
193        self.0 .0.as_deref_except().height as usize
194    }
195
196    /// Returns the sample aspect ratio of the frame.
197    pub fn sample_aspect_ratio(&self) -> Rational {
198        self.0 .0.as_deref_except().sample_aspect_ratio.into()
199    }
200
201    /// Sets the sample aspect ratio of the frame.
202    pub fn set_sample_aspect_ratio(&mut self, sample_aspect_ratio: impl Into<Rational>) {
203        self.0 .0.as_deref_mut_except().sample_aspect_ratio = sample_aspect_ratio.into().into();
204    }
205
206    /// Sets the width of the frame.
207    pub const fn set_width(&mut self, width: usize) {
208        self.0 .0.as_deref_mut_except().width = width as i32;
209    }
210
211    /// Sets the height of the frame.
212    pub const fn set_height(&mut self, height: usize) {
213        self.0 .0.as_deref_mut_except().height = height as i32;
214    }
215
216    /// Returns true if the frame is a keyframe.
217    pub const fn is_keyframe(&self) -> bool {
218        self.0 .0.as_deref_except().key_frame != 0
219    }
220
221    /// Returns the picture type of the frame.
222    pub const fn pict_type(&self) -> AVPictureType {
223        AVPictureType(self.0 .0.as_deref_except().pict_type as i32)
224    }
225
226    /// Sets the picture type of the frame.
227    pub const fn set_pict_type(&mut self, pict_type: AVPictureType) {
228        self.0 .0.as_deref_mut_except().pict_type = pict_type.0 as u32;
229    }
230
231    /// Returns the data of the frame. By specifying the index of the plane.
232    pub fn data(&self, index: usize) -> Option<&[u8]> {
233        let line = self.linesize(index)? as usize;
234        let height = self.height();
235        let raw = *self.0 .0.as_deref_except().data.get(index)?;
236
237        // Safety: The pointer here is valid & has the sizeof the `line * height`.
238        unsafe { Some(std::slice::from_raw_parts(raw, line * height)) }
239    }
240
241    /// Returns the data of the frame. By specifying the index of the plane.
242    pub fn data_mut(&mut self, index: usize) -> Option<&mut [u8]> {
243        let line = self.linesize(index)? as usize;
244        let height = self.height();
245        let raw = *self.0 .0.as_deref_mut_except().data.get(index)?;
246
247        // Safety: The pointer here is valid & has the sizeof the `line * height`.
248        unsafe { Some(std::slice::from_raw_parts_mut(raw, line * height)) }
249    }
250
251    /// Get the pixel format of the frame.
252    pub const fn format(&self) -> AVPixelFormat {
253        AVPixelFormat(self.0 .0.as_deref_except().format)
254    }
255}
256
257impl std::fmt::Debug for VideoFrame {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        f.debug_struct("VideoFrame")
260            .field("width", &self.width())
261            .field("height", &self.height())
262            .field("sample_aspect_ratio", &self.sample_aspect_ratio())
263            .field("pts", &self.pts())
264            .field("dts", &self.dts())
265            .field("duration", &self.duration())
266            .field("best_effort_timestamp", &self.best_effort_timestamp())
267            .field("time_base", &self.time_base())
268            .field("format", &self.format())
269            .field("is_audio", &self.is_audio())
270            .field("is_video", &self.is_video())
271            .field("is_keyframe", &self.is_keyframe())
272            .finish()
273    }
274}
275
276impl std::ops::Deref for VideoFrame {
277    type Target = GenericFrame;
278
279    fn deref(&self) -> &Self::Target {
280        &self.0
281    }
282}
283
284impl std::ops::DerefMut for VideoFrame {
285    fn deref_mut(&mut self) -> &mut Self::Target {
286        &mut self.0
287    }
288}
289
290/// A thin wrapper around `AVChannelLayout` to make it easier to use.
291pub struct AudioChannelLayout(SmartObject<AVChannelLayout>);
292
293impl Default for AudioChannelLayout {
294    fn default() -> Self {
295        // Safety: this is a c-struct and those are safe to zero out.
296        let zeroed_layout = unsafe { std::mem::zeroed() };
297
298        Self(SmartObject::new(zeroed_layout, Self::destructor))
299    }
300}
301
302impl AudioChannelLayout {
303    #[doc(hidden)]
304    fn destructor(ptr: &mut AVChannelLayout) {
305        // Safety: `av_channel_layout_uninit` is safe to call.
306        unsafe { av_channel_layout_uninit(ptr) };
307    }
308
309    /// Creates a new `AudioChannelLayout` instance.
310    pub fn new(channels: i32) -> Result<Self, FfmpegError> {
311        let mut layout = Self::default();
312
313        // Safety: `av_channel_layout_default` is safe to call.
314        unsafe { av_channel_layout_default(layout.0.as_mut(), channels) };
315
316        layout.validate()?;
317
318        Ok(layout)
319    }
320
321    /// Validates the channel layout.
322    pub fn validate(&self) -> Result<(), FfmpegError> {
323        // Safety: `av_channel_layout_check` is safe to call
324        if unsafe { av_channel_layout_check(self.0.as_ref()) } == 0 {
325            return Err(FfmpegError::Arguments("invalid channel layout"));
326        }
327
328        Ok(())
329    }
330
331    /// Wraps an `AVChannelLayout` automatically calling `av_channel_layout_uninit` on drop.
332    ///
333    /// # Safety
334    /// Requires that the layout can be safely deallocated with `av_channel_layout_uninit`
335    pub unsafe fn wrap(layout: AVChannelLayout) -> Self {
336        Self(SmartObject::new(layout, Self::destructor))
337    }
338
339    /// Returns the number of channels in the layout.
340    pub fn channel_count(&self) -> i32 {
341        self.0.as_ref().nb_channels
342    }
343
344    /// Consumes the `AudioChannelLayout` and returns the inner `AVChannelLayout`.
345    /// The caller is responsible for calling `av_channel_layout_uninit` on the returned value.
346    pub fn into_inner(self) -> AVChannelLayout {
347        self.0.into_inner()
348    }
349
350    pub(crate) fn apply(mut self, layout: &mut AVChannelLayout) {
351        std::mem::swap(layout, self.0.as_mut());
352    }
353}
354
355impl AudioFrame {
356    /// Sets channel layout to default with a channel count of `channel_count`.
357    pub fn set_channel_layout_default(&mut self, channel_count: usize) -> Result<(), FfmpegError> {
358        let layout = AudioChannelLayout::new(channel_count as i32)?;
359
360        // Safety: Our pointer is valid.
361        let av_frame = unsafe { self.as_mut_ptr().as_mut() }.ok_or(FfmpegError::Alloc)?;
362
363        layout.apply(&mut av_frame.ch_layout);
364
365        Ok(())
366    }
367
368    /// Sets channel layout to a custom layout. Note that the channel count
369    /// is defined by the given `crate::ffi::AVChannelLayout`.
370    pub fn set_channel_layout_custom(&mut self, custom_layout: AudioChannelLayout) -> Result<(), FfmpegError> {
371        custom_layout.validate()?;
372
373        // Safety: Our pointer is valid.
374        let av_frame = unsafe { self.as_mut_ptr().as_mut() }.ok_or(FfmpegError::Alloc)?;
375
376        custom_layout.apply(&mut av_frame.ch_layout);
377
378        Ok(())
379    }
380
381    /// Returns the channel layout of the frame.
382    pub const fn channel_layout(&self) -> AVChannelLayout {
383        self.0 .0.as_deref_except().ch_layout
384    }
385
386    /// Returns the channel count of the frame.
387    pub const fn channel_count(&self) -> usize {
388        self.0 .0.as_deref_except().ch_layout.nb_channels as usize
389    }
390
391    /// Returns the number of samples in the frame.
392    pub const fn nb_samples(&self) -> i32 {
393        self.0 .0.as_deref_except().nb_samples
394    }
395
396    /// Sets the number of samples in the frame.
397    pub const fn set_nb_samples(&mut self, nb_samples: usize) {
398        self.0 .0.as_deref_mut_except().nb_samples = nb_samples as i32;
399    }
400
401    /// Returns the sample rate of the frame.
402    pub const fn sample_rate(&self) -> i32 {
403        self.0 .0.as_deref_except().sample_rate
404    }
405
406    /// Sets the sample rate of the frame.
407    pub const fn set_sample_rate(&mut self, sample_rate: usize) {
408        self.0 .0.as_deref_mut_except().sample_rate = sample_rate as i32;
409    }
410}
411
412impl std::fmt::Debug for AudioFrame {
413    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414        f.debug_struct("AudioFrame")
415            .field("channel_count", &self.channel_count())
416            .field("nb_samples", &self.nb_samples())
417            .field("sample_rate", &self.sample_rate())
418            .field("pts", &self.pts())
419            .field("dts", &self.dts())
420            .field("duration", &self.duration())
421            .field("best_effort_timestamp", &self.best_effort_timestamp())
422            .field("time_base", &self.time_base())
423            .field("format", &self.format())
424            .field("is_audio", &self.is_audio())
425            .field("is_video", &self.is_video())
426            .finish()
427    }
428}
429
430impl std::ops::Deref for AudioFrame {
431    type Target = GenericFrame;
432
433    fn deref(&self) -> &Self::Target {
434        &self.0
435    }
436}
437
438impl std::ops::DerefMut for AudioFrame {
439    fn deref_mut(&mut self) -> &mut Self::Target {
440        &mut self.0
441    }
442}
443
444#[cfg(test)]
445#[cfg_attr(all(test, coverage_nightly), coverage(off))]
446mod tests {
447    use insta::assert_debug_snapshot;
448    use rand::Rng;
449    use rusty_ffmpeg::ffi::AVRational;
450
451    use crate::ffi::av_frame_get_buffer;
452    use crate::frame::{AudioChannelLayout, GenericFrame, VideoFrame};
453    use crate::rational::Rational;
454    use crate::{AVChannelOrder, AVPictureType, AVPixelFormat, AVSampleFormat};
455
456    #[test]
457    fn test_frame_clone() {
458        let mut frame = VideoFrame::new().expect("Failed to create frame");
459        frame.set_format(AVPixelFormat::Yuv420p.into());
460
461        // Safety: Our pointer is valid.
462        frame.set_width(16);
463        frame.set_height(16);
464
465        // Safety: `av_frame_get_buffer` is safe to call.
466        unsafe { frame.alloc_frame_buffer(Some(32)) }.expect("Failed to allocate frame buffer");
467
468        frame.set_pts(Some(12));
469        frame.set_dts(Some(34));
470        frame.set_duration(Some(5));
471        frame.set_time_base(Rational::static_new::<1, 30>());
472        frame.set_format(AVPixelFormat::Yuv420p.into());
473
474        let cloned_frame = frame.clone();
475
476        assert_eq!(
477            format!("{:?}", frame),
478            format!("{:?}", cloned_frame),
479            "Cloned frame should be equal to the original frame."
480        );
481    }
482
483    #[test]
484    fn test_audio_conversion() {
485        let frame = GenericFrame::new().expect("Failed to create frame");
486        let mut audio_frame = frame.audio();
487
488        assert!(audio_frame.set_channel_layout_default(2).is_ok());
489        assert!(audio_frame.is_audio(), "The frame should be identified as audio.");
490        assert!(!audio_frame.is_video(), "The frame should not be identified as video.");
491    }
492
493    #[test]
494    fn test_set_format() {
495        let mut frame = GenericFrame::new().expect("Failed to create frame");
496        frame.set_format(AVPixelFormat::Yuv420p.into());
497        assert_eq!(
498            frame.format(),
499            AVPixelFormat::Yuv420p.0,
500            "The format should match the set value."
501        );
502
503        frame.set_format(AVPixelFormat::Rgb24.into());
504        assert_eq!(
505            frame.format(),
506            AVPixelFormat::Rgb24.0,
507            "The format should match the updated value."
508        );
509    }
510
511    #[test]
512    fn test_linesize() {
513        let mut frame = VideoFrame::new().expect("Failed to create frame");
514        frame.set_format(AVPixelFormat::Yuv420p.into());
515        frame.set_width(1920);
516        frame.set_height(1080);
517
518        // Safety: `av_frame_get_buffer` is safe to call.
519        unsafe { frame.alloc_frame_buffer(Some(32)) }.expect("Failed to allocate frame buffer");
520
521        assert!(
522            frame.linesize(0).unwrap_or(0) > 0,
523            "Linesize should be greater than zero for valid index."
524        );
525
526        assert!(
527            frame.linesize(100).is_none(),
528            "Linesize at an invalid index should return None."
529        );
530    }
531
532    #[test]
533    fn test_frame_debug() {
534        let mut frame = GenericFrame::new().expect("Failed to create frame");
535        frame.set_pts(Some(12345));
536        frame.set_dts(Some(67890));
537        frame.set_duration(Some(1000));
538        frame.set_time_base(Rational::static_new::<1, 30>());
539        frame.set_format(AVPixelFormat::Yuv420p.into());
540
541        assert_debug_snapshot!(frame, @r"
542        GenericFrame {
543            pts: Some(
544                12345,
545            ),
546            dts: Some(
547                67890,
548            ),
549            duration: Some(
550                1000,
551            ),
552            best_effort_timestamp: Some(
553                12345,
554            ),
555            time_base: Rational {
556                numerator: 1,
557                denominator: 30,
558            },
559            format: 0,
560            is_audio: false,
561            is_video: false,
562        }
563        ");
564    }
565
566    #[test]
567    fn test_sample_aspect_ratio() {
568        let frame = GenericFrame::new().expect("Failed to create frame");
569        let mut video_frame = frame.video();
570        let sample_aspect_ratio = Rational::static_new::<16, 9>();
571        video_frame.set_sample_aspect_ratio(sample_aspect_ratio);
572
573        assert_eq!(
574            video_frame.sample_aspect_ratio(),
575            sample_aspect_ratio,
576            "Sample aspect ratio should match the set value."
577        );
578    }
579
580    #[test]
581    fn test_pict_type() {
582        let frame = GenericFrame::new().expect("Failed to create frame");
583        let mut video_frame = frame.video();
584        video_frame.set_pict_type(AVPictureType::Intra);
585
586        assert_eq!(
587            video_frame.pict_type(),
588            AVPictureType::Intra,
589            "Picture type should match the set value."
590        );
591    }
592
593    #[test]
594    fn test_data_allocation_and_access() {
595        let mut frame = GenericFrame::new().expect("Failed to create frame");
596        frame.set_format(AVPixelFormat::Yuv420p.into());
597        let mut video_frame = frame.video();
598        video_frame.set_width(16);
599        video_frame.set_height(16);
600
601        // Safety: Our pointer is valid.
602        let av_frame = unsafe { video_frame.as_mut_ptr().as_mut() }.expect("Failed to get mutable pointer");
603
604        // Safety: `av_frame_get_buffer` is safe to call.
605        unsafe {
606            assert!(av_frame_get_buffer(av_frame, 32) >= 0, "Failed to allocate buffer for frame.");
607        }
608
609        // randomize y-plane (data[0])
610        let linesize = av_frame.linesize[0] as usize; // bytes per row
611        let height = av_frame.height as usize; // total rows
612        let data_ptr = av_frame.data[0]; // pointer to the Y-plane data
613
614        let randomized_data = if !data_ptr.is_null() {
615            // Safety: `std::slice::from_raw_parts_mut` is safe to call.
616            let data_slice = unsafe { std::slice::from_raw_parts_mut(data_ptr, linesize * height) };
617            let mut rng = rand::rng();
618            rng.fill(data_slice);
619            data_slice.to_vec()
620        } else {
621            panic!("Failed to get valid data pointer for Y-plane.");
622        };
623
624        if let Some(data) = video_frame.data(0) {
625            assert_eq!(data, randomized_data.as_slice(), "Data does not match randomized content.");
626        } else {
627            panic!("Data at index 0 should not be None.");
628        }
629    }
630
631    #[test]
632    fn test_video_frame_debug() {
633        let mut frame = GenericFrame::new().expect("Failed to create frame");
634        frame.set_pts(Some(12345));
635        frame.set_dts(Some(67890));
636        frame.set_duration(Some(1000));
637        frame.set_time_base(AVRational { num: 1, den: 30 });
638        frame.set_format(AVPixelFormat::Yuv420p.into());
639        let mut video_frame = frame.video();
640        video_frame.set_width(1920);
641        video_frame.set_height(1080);
642        video_frame.set_sample_aspect_ratio(AVRational { num: 16, den: 9 });
643
644        assert_debug_snapshot!(video_frame, @r"
645        VideoFrame {
646            width: 1920,
647            height: 1080,
648            sample_aspect_ratio: Rational {
649                numerator: 16,
650                denominator: 9,
651            },
652            pts: Some(
653                12345,
654            ),
655            dts: Some(
656                67890,
657            ),
658            duration: Some(
659                1000,
660            ),
661            best_effort_timestamp: Some(
662                12345,
663            ),
664            time_base: Rational {
665                numerator: 1,
666                denominator: 30,
667            },
668            format: AVPixelFormat::Yuv420p,
669            is_audio: false,
670            is_video: true,
671            is_keyframe: false,
672        }
673        ");
674    }
675
676    #[test]
677    fn test_set_channel_layout_default_invalid_count_error() {
678        let frame = GenericFrame::new().expect("Failed to create frame");
679        let mut audio_frame = frame.audio();
680
681        assert!(
682            audio_frame.set_channel_layout_default(usize::MAX).is_err(),
683            "Expected error for invalid channel count."
684        );
685    }
686
687    #[test]
688    fn test_set_channel_layout_custom_invalid_layout_error() {
689        let frame = GenericFrame::new().expect("Failed to create frame");
690        let mut audio_frame = frame.audio();
691        // Safety: This is safe to be deallocated by the layout destructor.
692        let custom_layout = unsafe {
693            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
694                order: AVChannelOrder::Native.into(),
695                nb_channels: -1,
696                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 2 },
697                opaque: std::ptr::null_mut(),
698            })
699        };
700
701        assert!(
702            audio_frame.set_channel_layout_custom(custom_layout).is_err(),
703            "Expected error for invalid custom channel layout"
704        );
705    }
706
707    #[test]
708    fn test_set_channel_layout_custom() {
709        let frame = GenericFrame::new().expect("Failed to create frame");
710        let mut audio_frame = frame.audio();
711        // Safety: This is safe to be deallocated by the layout destructor.
712        let custom_layout = unsafe {
713            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
714                order: AVChannelOrder::Native.into(),
715                nb_channels: 2,
716                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 3 },
717                opaque: std::ptr::null_mut(),
718            })
719        };
720
721        assert!(
722            audio_frame.set_channel_layout_custom(custom_layout).is_ok(),
723            "Failed to set custom channel layout"
724        );
725
726        let layout = audio_frame.channel_layout();
727        assert_eq!(layout.nb_channels, 2, "Expected channel layout to have 2 channels (stereo).");
728        assert_eq!(
729            // Safety: this should be a mask not a pointer.
730            unsafe { layout.u.mask },
731            3,
732            "Expected channel mask to match AV_CH_LAYOUT_STEREO."
733        );
734        assert_eq!(
735            AVChannelOrder(layout.order),
736            AVChannelOrder::Native,
737            "Expected channel order to be AV_CHANNEL_ORDER_NATIVE."
738        );
739    }
740
741    #[test]
742    fn test_alloc_frame_buffer() {
743        let cases = [(None, true), (Some(0), true), (Some(32), true), (Some(-1), false)];
744
745        for alignment in cases {
746            let mut frame = GenericFrame::new().expect("Failed to create frame");
747            frame.set_format(AVSampleFormat::S16.into());
748            let mut audio_frame = frame.audio();
749            audio_frame.set_nb_samples(1024);
750            audio_frame.set_sample_rate(44100);
751
752            assert!(
753                audio_frame.set_channel_layout_default(2).is_ok(),
754                "Failed to set default channel layout"
755            );
756
757            assert_eq!(
758                // Safety: `audio_frame` is a valid pointer. And we dont attempt to read from the frame until after the allocation.
759                unsafe { audio_frame.alloc_frame_buffer(alignment.0).is_ok() },
760                alignment.1,
761                "Failed to allocate buffer with alignment {:?}",
762                alignment
763            );
764        }
765    }
766
767    #[test]
768    fn test_alloc_frame_buffer_error() {
769        let cases = [None, Some(0), Some(32), Some(-1)];
770
771        for alignment in cases {
772            let mut frame = GenericFrame::new().expect("Failed to create frame");
773            frame.set_format(AVSampleFormat::S16.into());
774            let mut audio_frame = frame.audio();
775            audio_frame.set_nb_samples(1024);
776
777            assert!(
778                // Safety: `audio_frame` is a valid pointer. And we dont attempt to read from the frame until after the allocation.
779                unsafe { audio_frame.alloc_frame_buffer(alignment).is_err() },
780                "Should fail to allocate buffer with invalid frame and alignment {:?}",
781                alignment
782            );
783        }
784    }
785
786    #[test]
787    fn test_nb_samples() {
788        let mut frame = GenericFrame::new().expect("Failed to create frame");
789        frame.set_format(AVSampleFormat::S16.into());
790        let mut audio_frame = frame.audio();
791        audio_frame.set_nb_samples(1024);
792
793        assert_eq!(
794            audio_frame.nb_samples(),
795            1024,
796            "The number of samples should match the set value."
797        );
798    }
799
800    #[test]
801    fn test_sample_rate() {
802        let mut frame = GenericFrame::new().expect("Failed to create frame");
803        frame.set_format(AVSampleFormat::S16.into());
804        let mut audio_frame = frame.audio();
805        audio_frame.set_sample_rate(44100);
806
807        assert_eq!(
808            audio_frame.sample_rate(),
809            44100,
810            "The sample rate should match the set value."
811        );
812    }
813
814    #[test]
815    fn test_audio_frame_debug() {
816        let mut frame = GenericFrame::new().expect("Failed to create frame");
817        frame.set_format(AVSampleFormat::S16.into());
818        let mut audio_frame = frame.audio();
819        audio_frame.set_nb_samples(1024);
820        audio_frame.set_sample_rate(44100);
821        audio_frame.set_pts(Some(12345));
822        audio_frame.set_dts(Some(67890));
823        audio_frame.set_duration(Some(512));
824        audio_frame.set_time_base(AVRational { num: 1, den: 44100 });
825
826        assert!(
827            audio_frame.set_channel_layout_default(2).is_ok(),
828            "Failed to set default channel layout"
829        );
830        assert_debug_snapshot!(audio_frame, @r"
831        AudioFrame {
832            channel_count: 2,
833            nb_samples: 1024,
834            sample_rate: 44100,
835            pts: Some(
836                12345,
837            ),
838            dts: Some(
839                67890,
840            ),
841            duration: Some(
842                512,
843            ),
844            best_effort_timestamp: Some(
845                12345,
846            ),
847            time_base: Rational {
848                numerator: 1,
849                denominator: 44100,
850            },
851            format: 1,
852            is_audio: true,
853            is_video: false,
854        }
855        ");
856    }
857}