scuffle_ffmpeg/
frame.rs

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/// Wrapper around the data buffers of AVFrame that handles bottom-to-top line iteration
13#[derive(Debug, PartialEq)]
14pub struct FrameData {
15    // this may point to the start of the last line of the buffer
16    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            // Safety: self.ptr + index is inside the bounds of the buffer
30            let ptr = unsafe { self.ptr.byte_add(index) };
31            // Safety: ptr is valid
32            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            // Safety: points to the start of the current line
38            let current_line_ptr = unsafe { self.ptr.byte_sub(line * stride) };
39            // Safety: points to the desired value within the current line
40            let value_ptr = unsafe { current_line_ptr.byte_add(line_pos) };
41            // Safety: value_ptr is valid
42            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            // Safety: self.ptr + index is inside the bounds of the buffer
54            let mut ptr = unsafe { self.ptr.byte_add(index) };
55            // Safety: ptr is valid
56            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            // Safety: points to the start of the current line
62            let current_line_ptr = unsafe { self.ptr.byte_sub(line * stride) };
63            // Safety: points to the desired value within the current line
64            let mut value_ptr = unsafe { current_line_ptr.byte_add(line_pos) };
65            // Safety: value_ptr is valid
66            unsafe { value_ptr.as_mut() }
67        }
68    }
69}
70
71impl FrameData {
72    /// Returns the height of the underlying data, in bytes
73    pub const fn height(&self) -> i32 {
74        self.height
75    }
76
77    /// Returns the linesize of the underlying data, in bytes. Negative if iteration
78    /// order is bottom-to-top. [Reference](https://ffmpeg.org/doxygen/7.0/structAVFrame.html#aa52bfc6605f6a3059a0c3226cc0f6567)
79    pub const fn linesize(&self) -> i32 {
80        self.linesize
81    }
82
83    /// Returns the length of the underlying data, in bytes
84    pub const fn len(&self) -> usize {
85        (self.linesize.abs() * self.height) as usize
86    }
87
88    /// Returns true if the underlying data buffer is empty
89    pub const fn is_empty(&self) -> bool {
90        self.len() == 0
91    }
92
93    /// Returns a reference to the byte at a given index
94    pub fn get(&self, index: usize) -> Option<&u8> {
95        if index < self.len() { Some(self.index(index)) } else { None }
96    }
97
98    /// Returns a mutable reference to the byte at a given index
99    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    /// Returns a slice of row `index`, respecting bottom-to-top iteration order
108    pub const fn get_row(&self, index: usize) -> Option<&[u8]> {
109        if index >= self.height as usize {
110            return None;
111        }
112
113        // Safety: this pointer is within bounds
114        let start_ptr = unsafe { self.ptr.byte_offset(self.linesize as isize * index as isize) };
115        // Safety: this slice is valid
116        Some(unsafe { core::slice::from_raw_parts(start_ptr.as_ptr(), self.linesize.unsigned_abs() as usize) })
117    }
118
119    /// Returns a mutable slice of row `index`, respecting bottom-to-top iteration order
120    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        // Safety: this pointer is within bounds
126        let start_ptr = unsafe { self.ptr.byte_offset(self.linesize as isize * index as isize) };
127        // Safety: this slice is valid
128        Some(unsafe { core::slice::from_raw_parts_mut(start_ptr.as_ptr(), self.linesize.unsigned_abs() as usize) })
129    }
130
131    /// Fills the data buffer with `value`
132    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
140/// A frame. Thin wrapper around [`AVFrame`].
141pub struct GenericFrame(SmartPtr<AVFrame>);
142
143impl Clone for GenericFrame {
144    fn clone(&self) -> Self {
145        // Safety: `av_frame_clone` is safe to call.
146        let clone = unsafe { av_frame_clone(self.0.as_ptr()) };
147
148        // Safety: The pointer here is valid.
149        unsafe { Self::wrap(clone).expect("failed to clone frame") }
150    }
151}
152
153/// Safety: `GenericFrame` is safe to send between threads.
154unsafe impl Send for GenericFrame {}
155
156/// Safety: `GenericFrame` is safe to share between threads.
157unsafe impl Sync for GenericFrame {}
158
159/// A video frame. Thin wrapper around [`GenericFrame`]. Like a frame but has specific video properties.
160#[derive(Clone)]
161pub struct VideoFrame(GenericFrame);
162
163/// An audio frame. Thin wrapper around [`GenericFrame`]. Like a frame but has specific audio properties.
164#[derive(Clone)]
165pub struct AudioFrame(GenericFrame);
166
167impl GenericFrame {
168    /// Creates a new frame.
169    pub(crate) fn new() -> Result<Self, FfmpegError> {
170        // Safety: `av_frame_alloc` is safe to call.
171        let frame = unsafe { av_frame_alloc() };
172
173        // Safety: The pointer here is valid.
174        unsafe { Self::wrap(frame).ok_or(FfmpegError::Alloc) }
175    }
176
177    /// Wraps a pointer to an `AVFrame`.
178    /// Takes ownership of the frame, meaning it will be freed when the [`GenericFrame`] is dropped.
179    ///
180    /// # Safety
181    /// `ptr` must be a valid pointer to an `AVFrame`.
182    pub(crate) unsafe fn wrap(ptr: *mut AVFrame) -> Option<Self> {
183        let destructor = |ptr: &mut *mut AVFrame| {
184            // Safety: av_frame_free is safe to call & we own the pointer.
185            unsafe { av_frame_free(ptr) }
186        };
187
188        // Safety: The safety comment of the function implies this is safe.
189        unsafe { SmartPtr::wrap_non_null(ptr, destructor).map(Self) }
190    }
191
192    /// Allocates a buffer for the frame.
193    ///
194    /// # Safety
195    /// This function is unsafe because the caller must ensure the frame has not been allocated yet.
196    /// Also the frame must be properly initialized after the allocation as the data is not zeroed out.
197    /// Therefore reading from the frame after allocation will result in reading uninitialized data.
198    pub(crate) unsafe fn alloc_frame_buffer(&mut self, alignment: Option<i32>) -> Result<(), FfmpegError> {
199        // Safety: `self.as_mut_ptr()` is assumed to provide a valid mutable pointer to an
200        // `AVFrame` structure. The `av_frame_get_buffer` function from FFMPEG allocates
201        // and attaches a buffer to the `AVFrame` if it doesn't already exist.
202        // It is the caller's responsibility to ensure that `self` is properly initialized
203        // and represents a valid `AVFrame` instance.
204        FfmpegErrorCode(unsafe { av_frame_get_buffer(self.as_mut_ptr(), alignment.unwrap_or(0)) }).result()?;
205        Ok(())
206    }
207
208    /// Returns a pointer to the frame.
209    pub(crate) const fn as_ptr(&self) -> *const AVFrame {
210        self.0.as_ptr()
211    }
212
213    /// Returns a mutable pointer to the frame.
214    pub(crate) const fn as_mut_ptr(&mut self) -> *mut AVFrame {
215        self.0.as_mut_ptr()
216    }
217
218    /// Make this frame a video frame.
219    pub(crate) const fn video(self) -> VideoFrame {
220        VideoFrame(self)
221    }
222
223    /// Make this frame an audio frame.
224    pub(crate) const fn audio(self) -> AudioFrame {
225        AudioFrame(self)
226    }
227
228    /// Returns the presentation timestamp of the frame, in `time_base` units.
229    pub const fn pts(&self) -> Option<i64> {
230        check_i64(self.0.as_deref_except().pts)
231    }
232
233    /// Sets the presentation timestamp of the frame, in `time_base` units.
234    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    /// Returns the duration of the frame, in `time_base` units.
240    pub const fn duration(&self) -> Option<i64> {
241        check_i64(self.0.as_deref_except().duration)
242    }
243
244    /// Sets the duration of the frame, in `time_base` units.
245    pub const fn set_duration(&mut self, duration: Option<i64>) {
246        self.0.as_deref_mut_except().duration = or_nopts(duration);
247    }
248
249    /// Returns the best effort timestamp of the frame, in `time_base` units.
250    pub const fn best_effort_timestamp(&self) -> Option<i64> {
251        check_i64(self.0.as_deref_except().best_effort_timestamp)
252    }
253
254    /// Returns the decoding timestamp of the frame, in `time_base` units.
255    pub const fn dts(&self) -> Option<i64> {
256        check_i64(self.0.as_deref_except().pkt_dts)
257    }
258
259    /// Sets the decoding timestamp of the frame, in `time_base` units.
260    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    /// Returns the time base of the frame.
265    pub fn time_base(&self) -> Rational {
266        self.0.as_deref_except().time_base.into()
267    }
268
269    /// Sets the time base of the frame.
270    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    /// Returns the format of the frame.
275    pub(crate) const fn format(&self) -> i32 {
276        self.0.as_deref_except().format
277    }
278
279    /// Returns true if the frame is an audio frame.
280    pub(crate) const fn is_audio(&self) -> bool {
281        self.0.as_deref_except().ch_layout.nb_channels != 0
282    }
283
284    /// Returns true if the frame is a video frame.
285    pub(crate) const fn is_video(&self) -> bool {
286        self.0.as_deref_except().width != 0
287    }
288
289    /// Returns the linesize of the frame, in bytes.
290    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    /// Creates a new [`VideoFrame`]
316    #[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        /// Alignment of the underlying data buffers, set to 0 for automatic.
327        #[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        // Safety: this is a brand new GenericFrame, with width, height and format set
352        unsafe { generic.alloc_frame_buffer(Some(alignment))? };
353
354        Ok(VideoFrame(generic))
355    }
356
357    /// Returns the width of the frame.
358    pub const fn width(&self) -> usize {
359        self.0.0.as_deref_except().width as usize
360    }
361
362    /// Returns the height of the frame.
363    pub const fn height(&self) -> usize {
364        self.0.0.as_deref_except().height as usize
365    }
366
367    /// Returns the sample aspect ratio of the frame.
368    pub fn sample_aspect_ratio(&self) -> Rational {
369        self.0.0.as_deref_except().sample_aspect_ratio.into()
370    }
371
372    /// Sets the sample aspect ratio of the frame.
373    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    /// Returns true if the frame is a keyframe.
378    pub const fn is_keyframe(&self) -> bool {
379        self.0.0.as_deref_except().key_frame != 0
380    }
381
382    /// Returns the picture type of the frame.
383    pub const fn pict_type(&self) -> AVPictureType {
384        AVPictureType(self.0.0.as_deref_except().pict_type as _)
385    }
386
387    /// Sets the picture type of the frame.
388    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    /// Returns a reference to the data of the frame. By specifying the index of the plane.
393    pub fn data(&self, index: usize) -> Option<Const<FrameData, '_>> {
394        // Safety: av_pix_fmt_desc_get is safe to call
395        let descriptor = unsafe { rusty_ffmpeg::ffi::av_pix_fmt_desc_get(self.format().into()) };
396        // Safety: as_ref is safe to call here
397        let descriptor = unsafe { descriptor.as_ref()? };
398
399        let line = self.linesize(index)?;
400        let height = {
401            // palette data
402            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    /// Returns a mutable reference to the data of the frame. By specifying the index of the plane.
421    pub fn data_mut(&mut self, index: usize) -> Option<Mut<FrameData, '_>> {
422        // Safety: av_pix_fmt_desc_get is safe to call
423        let descriptor = unsafe { rusty_ffmpeg::ffi::av_pix_fmt_desc_get(self.format().into()) };
424        // Safety: as_ref is safe to call here
425        let descriptor = unsafe { descriptor.as_ref()? };
426
427        let line = self.linesize(index)?;
428        let height = {
429            // palette data
430            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    /// Get the pixel format of the frame.
449    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
487/// A thin wrapper around `AVChannelLayout` to make it easier to use.
488pub struct AudioChannelLayout(SmartObject<AVChannelLayout>);
489
490impl Default for AudioChannelLayout {
491    fn default() -> Self {
492        // Safety: this is a c-struct and those are safe to zero out.
493        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        // Safety: `av_channel_layout_uninit` is safe to call.
503        unsafe { av_channel_layout_uninit(ptr) };
504    }
505
506    /// Creates a new `AudioChannelLayout` instance.
507    pub fn new(channels: i32) -> Result<Self, FfmpegError> {
508        let mut layout = Self::default();
509
510        // Safety: `av_channel_layout_default` is safe to call.
511        unsafe { av_channel_layout_default(layout.0.as_mut(), channels) };
512
513        layout.validate()?;
514
515        Ok(layout)
516    }
517
518    /// Copies this `AudioChannelLayout` instance.
519    pub fn copy(&self) -> Result<Self, FfmpegError> {
520        let mut new = Self::default();
521        // Safety: av_channel_layout_copy is safe to call
522        FfmpegErrorCode(unsafe { av_channel_layout_copy(new.0.inner_mut(), self.0.inner_ref()) }).result()?;
523        Ok(new)
524    }
525
526    /// Returns a pointer to the channel layout.
527    pub(crate) fn as_ptr(&self) -> *const AVChannelLayout {
528        self.0.as_ref()
529    }
530
531    /// Validates the channel layout.
532    pub fn validate(&self) -> Result<(), FfmpegError> {
533        // Safety: `av_channel_layout_check` is safe to call
534        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    /// Wraps an `AVChannelLayout` automatically calling `av_channel_layout_uninit` on drop.
542    ///
543    /// # Safety
544    /// Requires that the layout can be safely deallocated with `av_channel_layout_uninit`
545    pub unsafe fn wrap(layout: AVChannelLayout) -> Self {
546        Self(SmartObject::new(layout, Self::destructor))
547    }
548
549    /// Returns the number of channels in the layout.
550    pub fn channel_count(&self) -> i32 {
551        self.0.as_ref().nb_channels
552    }
553
554    /// Consumes the `AudioChannelLayout` and returns the inner `AVChannelLayout`.
555    /// The caller is responsible for calling `av_channel_layout_uninit` on the returned value.
556    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    /// Creates a new [`AudioFrame`]
568    #[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        /// Alignment of the underlying data buffers, set to 0 for automatic.
579        #[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        // Safety: this is a brand new GenericFrame, with nb_samples, ch_layout and format set
605        unsafe { generic.alloc_frame_buffer(Some(alignment))? };
606
607        Ok(Self(generic))
608    }
609
610    /// Returns the channel layout of the frame.
611    pub const fn channel_layout(&self) -> AVChannelLayout {
612        self.0.0.as_deref_except().ch_layout
613    }
614
615    /// Returns the channel count of the frame.
616    pub const fn channel_count(&self) -> usize {
617        self.0.0.as_deref_except().ch_layout.nb_channels as usize
618    }
619
620    /// Returns the number of samples in the frame.
621    pub const fn nb_samples(&self) -> i32 {
622        self.0.0.as_deref_except().nb_samples
623    }
624
625    /// Returns the sample rate of the frame.
626    pub const fn sample_rate(&self) -> i32 {
627        self.0.0.as_deref_except().sample_rate
628    }
629
630    /// Sets the sample rate of the frame.
631    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    /// Returns a reference to the data of the frame. By specifying the index of the plane.
636    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        // this is the length of the buffer ptr points to, in bytes
644        let linesize = self.linesize(index)?;
645
646        if linesize.is_negative() {
647            return None;
648        }
649
650        // Safety: ptr is not null and linesize is the correct length for the slice type
651        Some(unsafe { core::slice::from_raw_parts(ptr, linesize as usize) })
652    }
653
654    /// Returns a mutable reference to the data of the frame. By specifying the index of the plane.
655    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        // this is the length of the buffer ptr points to, in bytes
663        let linesize = self.linesize(index)?;
664
665        if linesize.is_negative() {
666            return None;
667        }
668
669        // Safety: ptr is not null and linesize is the correct length for the slice type
670        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>()) // generate random data
850                        .collect(),
851                );
852                data_slice.copy_from_slice(&randomized_data[row as usize]); // copy random data to the frame
853            }
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        // Safety: This is safe to be deallocated by the layout destructor.
921        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        // Safety: This is safe to be deallocated by the layout destructor.
942        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            // Safety: this should be a mask not a pointer.
963            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            // Safety: frame is not yet allocated
998            frame.0.as_deref_mut_except().format = AVSampleFormat::S16.into();
999            frame.0.as_deref_mut_except().nb_samples = 1024;
1000
1001            assert!(
1002                // Safety: `frame` is a valid pointer. And we dont attempt to read from the frame until after the allocation.
1003                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        // Safety: this is a valid pointer
1094        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        // Safety: this is a valid pointer
1118        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}