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}