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