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