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 fn channel_layout(&self) -> AudioChannelLayout {
612        // Safety: the AudioFrame has already been initialized at this point, so
613        // `av_channel_layout_uninit` is safe to call
614        unsafe { AudioChannelLayout::wrap(self.0.0.as_deref_except().ch_layout) }
615    }
616
617    /// Returns the channel count of the frame.
618    pub const fn channel_count(&self) -> usize {
619        self.0.0.as_deref_except().ch_layout.nb_channels as usize
620    }
621
622    /// Returns the number of samples in the frame.
623    pub const fn nb_samples(&self) -> i32 {
624        self.0.0.as_deref_except().nb_samples
625    }
626
627    /// Returns the sample rate of the frame.
628    pub const fn sample_rate(&self) -> i32 {
629        self.0.0.as_deref_except().sample_rate
630    }
631
632    /// Sets the sample rate of the frame.
633    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    /// Returns a reference to the data of the frame. By specifying the index of the plane.
638    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        // this is the length of the buffer ptr points to, in bytes
646        let linesize = self.linesize(index)?;
647
648        if linesize.is_negative() {
649            return None;
650        }
651
652        // Safety: ptr is not null and linesize is the correct length for the slice type
653        Some(unsafe { core::slice::from_raw_parts(ptr, linesize as usize) })
654    }
655
656    /// Returns a mutable reference to the data of the frame. By specifying the index of the plane.
657    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        // this is the length of the buffer ptr points to, in bytes
665        let linesize = self.linesize(index)?;
666
667        if linesize.is_negative() {
668            return None;
669        }
670
671        // Safety: ptr is not null and linesize is the correct length for the slice type
672        Some(unsafe { core::slice::from_raw_parts_mut(ptr, linesize as usize) })
673    }
674
675    /// Get the sample format of the frame.
676    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>()) // generate random data
857                        .collect(),
858                );
859                data_slice.copy_from_slice(&randomized_data[row as usize]); // copy random data to the frame
860            }
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        // Safety: This is safe to be deallocated by the layout destructor.
928        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        // Safety: This is safe to be deallocated by the layout destructor.
949        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            // Safety: this should be a mask not a pointer.
974            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            // Safety: frame is not yet allocated
1009            frame.0.as_deref_mut_except().format = AVSampleFormat::S16.into();
1010            frame.0.as_deref_mut_except().nb_samples = 1024;
1011
1012            assert!(
1013                // Safety: `frame` is a valid pointer. And we dont attempt to read from the frame until after the allocation.
1014                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        // Safety: this is a valid pointer
1105        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        // Safety: this is a valid pointer
1129        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}