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}