Skip to main content

oxideav_core/
packet.rs

1//! Compressed-data packet passed between demuxer → decoder and encoder → muxer.
2
3use crate::time::TimeBase;
4
5/// Metadata flags on a packet.
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub struct PacketFlags {
8    /// Packet is (or starts) a keyframe / random-access point.
9    pub keyframe: bool,
10    /// Packet holds codec-level headers rather than media data.
11    pub header: bool,
12    /// Packet's data may be corrupt but decode should still be attempted.
13    pub corrupt: bool,
14    /// Packet should be discarded (e.g., decoder delay padding).
15    pub discard: bool,
16    /// Packet is the last in its source container's natural framing unit
17    /// (Ogg page, MP4 chunk, MKV cluster, …). Container muxers may use this
18    /// signal to recreate similar boundaries in their output. Decoders
19    /// should ignore it.
20    pub unit_boundary: bool,
21}
22
23/// A chunk of compressed (encoded) data belonging to one stream.
24#[derive(Clone, Debug)]
25pub struct Packet {
26    /// Stream index this packet belongs to.
27    pub stream_index: u32,
28    /// Time base in which `pts` and `dts` are expressed.
29    pub time_base: TimeBase,
30    /// Presentation timestamp (display order). `None` if unknown.
31    pub pts: Option<i64>,
32    /// Decode timestamp (decode order). Often equal to `pts` for intra-only codecs.
33    pub dts: Option<i64>,
34    /// Packet duration in `time_base` units, or `None` if unknown.
35    pub duration: Option<i64>,
36    /// Flags describing this packet.
37    pub flags: PacketFlags,
38    /// Compressed payload.
39    pub data: Vec<u8>,
40}
41
42impl Packet {
43    pub fn new(stream_index: u32, time_base: TimeBase, data: Vec<u8>) -> Self {
44        Self {
45            stream_index,
46            time_base,
47            pts: None,
48            dts: None,
49            duration: None,
50            flags: PacketFlags::default(),
51            data,
52        }
53    }
54
55    pub fn with_pts(mut self, pts: i64) -> Self {
56        self.pts = Some(pts);
57        self
58    }
59
60    pub fn with_dts(mut self, dts: i64) -> Self {
61        self.dts = Some(dts);
62        self
63    }
64
65    pub fn with_duration(mut self, d: i64) -> Self {
66        self.duration = Some(d);
67        self
68    }
69
70    pub fn with_keyframe(mut self, kf: bool) -> Self {
71        self.flags.keyframe = kf;
72        self
73    }
74
75    /// Mark this packet as carrying codec-level headers rather than
76    /// media data (extradata, parameter sets, codec-private blobs).
77    pub fn with_header(mut self, header: bool) -> Self {
78        self.flags.header = header;
79        self
80    }
81
82    /// Mark this packet's payload as possibly corrupt. Decoders should
83    /// still attempt to decode it but may produce best-effort output.
84    pub fn with_corrupt(mut self, corrupt: bool) -> Self {
85        self.flags.corrupt = corrupt;
86        self
87    }
88
89    /// Mark this packet for downstream discard (e.g. decoder delay
90    /// padding, encoder priming samples, ASS dialogue tags shipped only
91    /// for muxer round-trip).
92    pub fn with_discard(mut self, discard: bool) -> Self {
93        self.flags.discard = discard;
94        self
95    }
96
97    /// Mark this packet as the last entry inside its source container's
98    /// natural framing unit (Ogg page, MP4 chunk, MKV cluster). Decoders
99    /// ignore the flag; muxers may use it to recreate similar
100    /// boundaries in their output.
101    pub fn with_unit_boundary(mut self, boundary: bool) -> Self {
102        self.flags.unit_boundary = boundary;
103        self
104    }
105
106    /// Replace this packet's full flag set in one call. Useful for
107    /// demuxers that compute flags up front and want a single setter
108    /// rather than four chained builder calls.
109    pub fn with_flags(mut self, flags: PacketFlags) -> Self {
110        self.flags = flags;
111        self
112    }
113
114    /// Override the packet's stream index. Builder-style chainable
115    /// counterpart to the public field, for cases where the demuxer
116    /// builds packets with a placeholder stream index and remaps them
117    /// to the final index downstream.
118    pub fn with_stream_index(mut self, stream_index: u32) -> Self {
119        self.stream_index = stream_index;
120        self
121    }
122
123    /// Override the packet's time base. Builder-style chainable
124    /// counterpart to the public field, for cases where the time base
125    /// isn't known at construction time (e.g. a remuxer rescaling all
126    /// packets onto a unified output base).
127    pub fn with_time_base(mut self, time_base: TimeBase) -> Self {
128        self.time_base = time_base;
129        self
130    }
131
132    /// Compute the packet's end PTS (`pts + duration`) when both are
133    /// known. Returns `None` if either is missing, or if the sum would
134    /// overflow `i64`. Useful for muxers that need to derive a per-
135    /// packet end timestamp without recomputing it at every call site.
136    pub fn end_pts(&self) -> Option<i64> {
137        self.pts
138            .zip(self.duration)
139            .and_then(|(p, d)| p.checked_add(d))
140    }
141
142    /// Convenience accessor: `true` when [`PacketFlags::keyframe`] is
143    /// set. Mirrors the builder pair `with_keyframe(true)`.
144    pub fn is_keyframe(&self) -> bool {
145        self.flags.keyframe
146    }
147
148    /// Convenience accessor: `true` when [`PacketFlags::header`] is set
149    /// (the packet carries codec-level headers rather than media data).
150    pub fn is_header(&self) -> bool {
151        self.flags.header
152    }
153
154    /// Convenience accessor: `true` when [`PacketFlags::discard`] is
155    /// set (downstream consumers should drop the packet).
156    pub fn is_discard(&self) -> bool {
157        self.flags.discard
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    fn tb() -> TimeBase {
166        TimeBase::new(1, 1000)
167    }
168
169    #[test]
170    fn new_packet_has_default_flags_and_no_timing() {
171        let p = Packet::new(3, tb(), vec![1, 2, 3]);
172        assert_eq!(p.stream_index, 3);
173        assert_eq!(p.time_base, tb());
174        assert!(p.pts.is_none());
175        assert!(p.dts.is_none());
176        assert!(p.duration.is_none());
177        assert_eq!(p.flags, PacketFlags::default());
178        assert_eq!(p.data, vec![1, 2, 3]);
179        // All accessor convenience helpers default to false.
180        assert!(!p.is_keyframe());
181        assert!(!p.is_header());
182        assert!(!p.is_discard());
183    }
184
185    #[test]
186    fn builder_chain_sets_every_flag_field() {
187        let p = Packet::new(0, tb(), vec![])
188            .with_keyframe(true)
189            .with_header(true)
190            .with_corrupt(true)
191            .with_discard(true)
192            .with_unit_boundary(true);
193        assert!(p.flags.keyframe);
194        assert!(p.flags.header);
195        assert!(p.flags.corrupt);
196        assert!(p.flags.discard);
197        assert!(p.flags.unit_boundary);
198        assert!(p.is_keyframe());
199        assert!(p.is_header());
200        assert!(p.is_discard());
201    }
202
203    #[test]
204    fn with_flags_replaces_full_flag_set() {
205        let flags = PacketFlags {
206            keyframe: true,
207            header: false,
208            corrupt: true,
209            discard: false,
210            unit_boundary: true,
211        };
212        let p = Packet::new(0, tb(), vec![]).with_flags(flags);
213        assert_eq!(p.flags, flags);
214        // A second with_flags wipes the prior set rather than OR-ing.
215        let cleared = p.with_flags(PacketFlags::default());
216        assert_eq!(cleared.flags, PacketFlags::default());
217    }
218
219    #[test]
220    fn with_stream_index_and_time_base_override() {
221        let original = TimeBase::new(1, 1);
222        let replacement = TimeBase::new(1, 90_000);
223        let p = Packet::new(0, original, vec![])
224            .with_stream_index(7)
225            .with_time_base(replacement);
226        assert_eq!(p.stream_index, 7);
227        assert_eq!(p.time_base, replacement);
228    }
229
230    #[test]
231    fn end_pts_requires_both_pts_and_duration() {
232        // Neither set.
233        assert_eq!(Packet::new(0, tb(), vec![]).end_pts(), None);
234        // pts only.
235        assert_eq!(Packet::new(0, tb(), vec![]).with_pts(100).end_pts(), None);
236        // duration only.
237        assert_eq!(
238            Packet::new(0, tb(), vec![]).with_duration(50).end_pts(),
239            None
240        );
241        // Both: returns pts + duration.
242        assert_eq!(
243            Packet::new(0, tb(), vec![])
244                .with_pts(100)
245                .with_duration(50)
246                .end_pts(),
247            Some(150)
248        );
249    }
250
251    #[test]
252    fn end_pts_saturates_on_overflow() {
253        // pts + duration would overflow i64::MAX; checked_add returns
254        // None so end_pts surfaces that instead of wrapping.
255        let p = Packet::new(0, tb(), vec![])
256            .with_pts(i64::MAX - 1)
257            .with_duration(10);
258        assert_eq!(p.end_pts(), None);
259    }
260
261    #[test]
262    fn end_pts_handles_negative_pts() {
263        // Negative pts is legal (B-frames pre-roll); ensure the sum
264        // still works through zero.
265        let p = Packet::new(0, tb(), vec![]).with_pts(-25).with_duration(40);
266        assert_eq!(p.end_pts(), Some(15));
267    }
268}