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}