Skip to main content

oxideav_core/
frame.rs

1//! Uncompressed audio and video frames.
2
3use crate::format::{PixelFormat, SampleFormat};
4use crate::subtitle::SubtitleCue;
5use crate::time::TimeBase;
6
7/// A decoded chunk of uncompressed data: either audio samples, a video
8/// picture, or (for subtitle streams) a single styled cue.
9///
10/// Marked `#[non_exhaustive]` — consumers that match on variants must
11/// include a wildcard arm. This lets the crate add new frame kinds (data
12/// tracks, hap rops, …) without breaking downstream code.
13#[derive(Clone, Debug)]
14#[non_exhaustive]
15pub enum Frame {
16    Audio(AudioFrame),
17    Video(VideoFrame),
18    /// A single subtitle cue. Timing is carried inside the cue itself
19    /// (`start_us`/`end_us`) so it's independent of container time bases,
20    /// but the enclosing pipeline/muxer can still rescale via `pts` at
21    /// the packet layer.
22    Subtitle(SubtitleCue),
23}
24
25impl Frame {
26    pub fn pts(&self) -> Option<i64> {
27        match self {
28            Self::Audio(a) => a.pts,
29            Self::Video(v) => v.pts,
30            Self::Subtitle(s) => Some(s.start_us),
31        }
32    }
33
34    pub fn time_base(&self) -> TimeBase {
35        match self {
36            Self::Audio(a) => a.time_base,
37            Self::Video(v) => v.time_base,
38            // Subtitle cues carry raw microseconds. Expose a 1/1_000_000
39            // base so the value lines up with the pts() result above.
40            Self::Subtitle(_) => TimeBase::new(1, 1_000_000),
41        }
42    }
43}
44
45/// Uncompressed audio frame.
46///
47/// Sample layout is determined by `format`:
48/// - Interleaved formats: `data` has one plane; samples are stored as
49///   `ch0 ch1 ... chN ch0 ch1 ... chN ...`.
50/// - Planar formats: `data` has one plane per channel.
51#[derive(Clone, Debug)]
52pub struct AudioFrame {
53    pub format: SampleFormat,
54    pub channels: u16,
55    pub sample_rate: u32,
56    /// Number of samples *per channel*.
57    pub samples: u32,
58    pub pts: Option<i64>,
59    pub time_base: TimeBase,
60    /// Raw sample bytes. `.len() == planes()` — i.e. one element per plane.
61    pub data: Vec<Vec<u8>>,
62}
63
64impl AudioFrame {
65    pub fn planes(&self) -> usize {
66        if self.format.is_planar() {
67            self.channels as usize
68        } else {
69            1
70        }
71    }
72}
73
74/// Uncompressed video frame.
75#[derive(Clone, Debug)]
76pub struct VideoFrame {
77    pub format: PixelFormat,
78    pub width: u32,
79    pub height: u32,
80    pub pts: Option<i64>,
81    pub time_base: TimeBase,
82    /// One entry per plane (e.g., 3 for Yuv420P). Each entry is `(stride, bytes)`.
83    pub planes: Vec<VideoPlane>,
84}
85
86#[derive(Clone, Debug)]
87pub struct VideoPlane {
88    /// Bytes per row in `data`.
89    pub stride: usize,
90    pub data: Vec<u8>,
91}