symphonia_core/packet.rs
1// Symphonia
2// Copyright (c) 2019-2026 The Project Symphonia Developers.
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8//! The `packet` module defines the packet structure.
9
10use crate::io::BufReader;
11use crate::units::{Duration, Timestamp};
12
13/// A `Packet` contains a discrete amount of encoded data for a single codec bitstream. The exact
14/// amount of data is bounded, but not defined, and is dependant on the container and/or the
15/// encapsulated codec.
16///
17/// # Timing
18///
19/// Packets carry various timing information to support the decoding process. Generally, this
20/// timing information is read directly from the media container but it may also be synthesized by
21/// a format reader if it is not explicitly signalled.
22///
23/// * **Presentation Timestamp (PTS):** The time relative to the start of the stream that the
24/// decoded packet should be presented.
25///
26/// * **Decode Timestamp (DTS):** The time relative to the start of the stream that the packet
27/// should be decoded. The DTS is usually the same as PTS for audio.
28///
29/// * **Duration:** The duration of all *valid* frames in the packet. Equal to the duration of all
30/// *decoded* frames if there is no trimming.
31///
32/// * **Trim Start/End:** The duration of frames that should be trimmed from the start/end of the
33/// decoded packet before presentation. The sum of the duration and trim start/end equals the
34/// duration of *decoded* frames.
35///
36/// Take note of the difference between *valid* and *decoded* frames. Valid frames are frames that
37/// should be presented (played back) to the user, while decoded frames include any encoder delay
38/// and/or padding frames. The latter are generally discarded by the decoder. The duration of all
39/// *decoded* frames is also called the block duration.
40///
41/// # For Implementers
42///
43/// When synthesizing PTS, negative PTS should be used for encoder delay frames. However, this is
44/// not strictly mandatory. Encoder delay and padding frames are ultimately signalled using the trim
45/// fields. Regardless, the `dur` field must only be populated with the duration of *valid* frames,
46/// while the sum of `dur`, `trim_start`, and `trim_end` must equal the amount of frames the decoder
47/// would produce if no trimming occurs.
48#[derive(Clone)]
49#[non_exhaustive]
50pub struct Packet {
51 /// The track ID of the track this packet belongs to.
52 pub track_id: u32,
53 /// The presentation timestamp (PTS) of the packet in `TimeBase` units.+
54 ///
55 /// This is the time relative to the start of the media that the decoded packet should be
56 /// presented to the user.
57 pub pts: Timestamp,
58 /// The decode timestamp (DTS) of the packet in `TimeBase` units.
59 ///
60 /// This is the time relative to the start of the media that the packet should be decoded.
61 pub dts: Timestamp,
62 /// The duration of all *valid* frames in the packet in `TimeBase` units.
63 ///
64 /// This duration excludes any delay or padding frames that may be produced by the decoder.
65 /// Generally, delay or padding frames should not be presented to the user.
66 pub dur: Duration,
67 /// The duration of *decoded* frames that should be trimmed from the start of the decoded
68 /// buffer to remove encoder delay.
69 pub trim_start: Duration,
70 /// The duration of *decoded* frames that should be trimmed from the end of the decoded
71 /// buffer to remove encoder padding.
72 pub trim_end: Duration,
73 /// The packet data buffer.
74 pub data: Box<[u8]>,
75}
76
77impl Packet {
78 /// Create a new untrimmed `Packet`.
79 pub fn new(track_id: u32, pts: Timestamp, dur: Duration, data: impl Into<Box<[u8]>>) -> Self {
80 Packet {
81 track_id,
82 pts,
83 dts: pts,
84 dur,
85 trim_start: Duration::ZERO,
86 trim_end: Duration::ZERO,
87 data: data.into(),
88 }
89 }
90
91 /// Get the duration of all *decoded* frames in the packet in `TimeBase` units.
92 ///
93 /// This duration includes any delay or padding frames that may be produced by the decoder. As
94 /// such, this is a sum of the duration, start trim, and end trim.
95 #[inline]
96 pub const fn block_dur(&self) -> Duration {
97 self.dur.saturating_add(self.trim_start).saturating_add(self.trim_end)
98 }
99
100 /// Get a `BufReader` to read the packet data buffer sequentially.
101 #[inline]
102 pub fn as_buf_reader(&self) -> BufReader<'_> {
103 BufReader::new(&self.data)
104 }
105
106 /// Get a `PacketRef` borrowing this packet's data buffer.
107 #[inline]
108 pub fn as_packet_ref(&self) -> PacketRef<'_> {
109 PacketRef {
110 track_id: self.track_id,
111 pts: self.pts,
112 dts: self.dts,
113 dur: self.dur,
114 trim_start: self.trim_start,
115 trim_end: self.trim_end,
116 data: &self.data,
117 }
118 }
119}
120
121impl std::fmt::Debug for Packet {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("Packet")
124 .field("track_id", &self.track_id)
125 .field("pts", &self.pts)
126 .field("dts", &self.dts)
127 .field("dur", &self.dur)
128 .field("trim_start", &self.trim_start)
129 .field("trim_end", &self.trim_end)
130 // Omit the data buffer contents.
131 .field("data", &format_args!("<{} bytes>", self.data.len()))
132 .finish()
133 }
134}
135
136/// A non-owning equivalent to `Packet`.
137///
138/// `PacketRef` is the zero-copy, non-owning, equivalent of `Packet`. It is particularly useful when
139/// packet data is already residing in an application-managed fixed buffer or when data is
140/// passed in from external environments like C/C++ via FFI. This allows decoders to process
141/// the data directly without forcing a heap allocation and deep copy.
142///
143/// See [`Packet`] for more details on the various timing and implementation details.
144#[derive(Clone, Copy)]
145#[non_exhaustive]
146pub struct PacketRef<'a> {
147 /// The track ID of the track this packet belongs to.
148 pub track_id: u32,
149 /// The presentation timestamp (PTS) of the packet in `TimeBase` units.+
150 ///
151 /// This is the time relative to the start of the media that the decoded packet should be
152 /// presented to the user.
153 pub pts: Timestamp,
154 /// The decode timestamp (DTS) of the packet in `TimeBase` units.
155 ///
156 /// This is the time relative to the start of the media that the packet should be decoded.
157 pub dts: Timestamp,
158 /// The duration of all *valid* frames in the packet in `TimeBase` units.
159 ///
160 /// This duration excludes any delay or padding frames that may be produced by the decoder.
161 /// Generally, delay or padding frames should not be presented to the user.
162 pub dur: Duration,
163 /// The duration of *decoded* frames that should be trimmed from the start of the decoded
164 /// buffer to remove encoder delay.
165 pub trim_start: Duration,
166 /// The duration of *decoded* frames that should be trimmed from the end of the decoded
167 /// buffer to remove encoder padding.
168 pub trim_end: Duration,
169 /// The packet data buffer.
170 pub data: &'a [u8],
171}
172
173impl<'a> PacketRef<'a> {
174 /// Create a new untrimmed `PacketRef`.
175 ///
176 /// The `data` slice can be constructed directly from a fixed-size buffer, or from
177 /// raw FFI pointers (e.g., a C++ `std::vector` payload) using `std::slice::from_raw_parts`.
178 pub fn new(track_id: u32, pts: Timestamp, dur: Duration, data: &'a [u8]) -> Self {
179 PacketRef {
180 track_id,
181 pts,
182 dts: pts,
183 dur,
184 trim_start: Duration::ZERO,
185 trim_end: Duration::ZERO,
186 data,
187 }
188 }
189
190 /// Get the duration of all *decoded* frames in the packet in `TimeBase` units.
191 #[inline]
192 pub const fn block_dur(&self) -> Duration {
193 self.dur.saturating_add(self.trim_start).saturating_add(self.trim_end)
194 }
195
196 /// Get a `BufReader` to read the packet data buffer sequentially.
197 #[inline]
198 pub fn as_buf_reader(&self) -> BufReader<'_> {
199 BufReader::new(self.data)
200 }
201}
202
203impl std::fmt::Debug for PacketRef<'_> {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.debug_struct("PacketRef")
206 .field("track_id", &self.track_id)
207 .field("pts", &self.pts)
208 .field("dts", &self.dts)
209 .field("dur", &self.dur)
210 .field("trim_start", &self.trim_start)
211 .field("trim_end", &self.trim_end)
212 // Omit the data buffer contents.
213 .field("data", &format_args!("<{} bytes>", self.data.len()))
214 .finish()
215 }
216}
217
218impl<'a> From<&'a Packet> for PacketRef<'a> {
219 fn from(packet: &'a Packet) -> Self {
220 packet.as_packet_ref()
221 }
222}
223
224mod builder {
225 use crate::packet::{Packet, PacketRef};
226 use crate::units::{Duration, Timestamp};
227
228 pub struct HasTrackId(u32);
229 pub struct NoTrackId;
230
231 pub struct HasPts(Timestamp);
232 pub struct NoPts;
233
234 pub struct HasDur(Duration);
235 pub struct NoDur;
236
237 pub struct HasBuf(Box<[u8]>);
238 pub struct HasBufRef<'a>(&'a [u8]);
239 pub struct NoBuf;
240
241 /// A builder for creating owning or non-owning packets.
242 ///
243 /// See [`Packet`] for a detailed description of all packet fields.
244 ///
245 /// The track ID, PTS, duration, and data fields are mandatory and must be provided before a
246 /// packet can be built.
247 pub struct PacketBuilder<T, P, D, B> {
248 track_id: T,
249 pts: P,
250 dur: D,
251 buf: B,
252 dts: Option<Timestamp>,
253 trim_start: Duration,
254 trim_end: Duration,
255 }
256
257 impl Default for PacketBuilder<NoTrackId, NoPts, NoDur, NoBuf> {
258 fn default() -> Self {
259 Self::new()
260 }
261 }
262
263 impl PacketBuilder<NoTrackId, NoPts, NoDur, NoBuf> {
264 /// Create the packet builder.
265 pub fn new() -> Self {
266 Self {
267 track_id: NoTrackId,
268 pts: NoPts,
269 dur: NoDur,
270 buf: NoBuf,
271 dts: None,
272 trim_start: Duration::ZERO,
273 trim_end: Duration::ZERO,
274 }
275 }
276 }
277
278 impl PacketBuilder<HasTrackId, HasPts, HasDur, HasBuf> {
279 /// Build the packet.
280 pub fn build(self) -> Packet {
281 Packet {
282 track_id: self.track_id.0,
283 pts: self.pts.0,
284 dts: self.dts.unwrap_or(self.pts.0),
285 dur: self.dur.0,
286 trim_start: self.trim_start,
287 trim_end: self.trim_end,
288 data: self.buf.0,
289 }
290 }
291 }
292
293 impl<'a> PacketBuilder<HasTrackId, HasPts, HasDur, HasBufRef<'a>> {
294 /// Build a non-owning packet.
295 pub fn build_packet_ref(self) -> PacketRef<'a> {
296 PacketRef {
297 track_id: self.track_id.0,
298 pts: self.pts.0,
299 dts: self.dts.unwrap_or(self.pts.0),
300 dur: self.dur.0,
301 trim_start: self.trim_start,
302 trim_end: self.trim_end,
303 data: self.buf.0,
304 }
305 }
306 }
307
308 impl<T, B> PacketBuilder<T, HasPts, NoDur, B> {
309 /// Provide the packet's duration and calculate the trim fields.
310 ///
311 /// Given the provided duration including delay and padding frames, the stream end timestamp
312 /// (optional), and the previously provided PTS, this function calculates the packet's
313 /// duration, trim start, and trim end.
314 ///
315 /// This helper assumes that all frames with a PTS < 0 are to be trimmed from the start.
316 /// Frames whose PTS exceeds the end timestamp of the stream are trimmed from the end. The
317 /// trim end will only be calculated if the stream's end timestamp is provided.
318 pub fn trimmed_dur(
319 self,
320 block_dur: Duration,
321 end_pts: Option<Timestamp>,
322 ) -> PacketBuilder<T, HasPts, HasDur, B> {
323 let Self { track_id, pts, buf, dts, .. } = self;
324
325 // All frames with a negative PTS must be trimmed first. This duration may exceed the
326 // number of decoded frames.
327 let negative = pts.0.duration_to(Timestamp::ZERO).unwrap_or(Duration::ZERO);
328
329 // Cap to the number of decoded frames.
330 let trim_start = negative.min(block_dur);
331 let mut trim_end = Duration::ZERO;
332
333 // It is only possible to trim the end of a packet if the end PTS is known.
334 if let Some(end_pts) = end_pts {
335 if let Some(pkt_end_pts) = pts.0.checked_add(block_dur) {
336 trim_end = pkt_end_pts.duration_from(end_pts).unwrap_or(Duration::ZERO);
337 }
338 }
339
340 let dur = block_dur.saturating_sub(self.trim_start).saturating_sub(self.trim_end);
341
342 PacketBuilder { track_id, pts, dur: HasDur(dur), buf, dts, trim_start, trim_end }
343 }
344 }
345
346 impl<T, P, B> PacketBuilder<T, P, NoDur, B> {
347 /// Provide the packet's duration including delay and padding frames.
348 pub fn dur(self, dur: Duration) -> PacketBuilder<T, P, HasDur, B> {
349 let Self { track_id, pts, buf, dts, trim_start, trim_end, .. } = self;
350 PacketBuilder { track_id, pts, dur: HasDur(dur), buf, dts, trim_start, trim_end }
351 }
352 }
353
354 impl<T, P, D, B> PacketBuilder<T, P, D, B> {
355 /// Provide the track ID.
356 pub fn track_id(self, track_id: u32) -> PacketBuilder<HasTrackId, P, D, B> {
357 let Self { pts, dur, buf, dts, trim_start, trim_end, .. } = self;
358 PacketBuilder {
359 track_id: HasTrackId(track_id),
360 pts,
361 dur,
362 buf,
363 dts,
364 trim_start,
365 trim_end,
366 }
367 }
368
369 /// Provide the presentation timestamp (PTS).
370 pub fn pts(self, pts: Timestamp) -> PacketBuilder<T, HasPts, D, B> {
371 let Self { track_id, dur, buf, dts, trim_start, trim_end, .. } = self;
372 PacketBuilder { track_id, pts: HasPts(pts), dur, buf, dts, trim_start, trim_end }
373 }
374
375 /// Provide the packet's data buffer.
376 ///
377 /// When holding an owned data buffer, an owning `Packet` is built.
378 pub fn data(self, buf: impl Into<Box<[u8]>>) -> PacketBuilder<T, P, D, HasBuf> {
379 let Self { track_id, pts, dur, dts, trim_start, trim_end, .. } = self;
380 PacketBuilder { track_id, pts, dur, buf: HasBuf(buf.into()), dts, trim_start, trim_end }
381 }
382
383 /// Provide the packet's data buffer as a non-owning reference.
384 ///
385 /// When holding a non-owning data buffer reference, a non-owning `PacketRef` is built.
386 pub fn data_by_ref<'a>(self, buf: &'a [u8]) -> PacketBuilder<T, P, D, HasBufRef<'a>> {
387 let Self { track_id, pts, dur, dts, trim_start, trim_end, .. } = self;
388 PacketBuilder { track_id, pts, dur, buf: HasBufRef(buf), dts, trim_start, trim_end }
389 }
390
391 /// Provide the decode timestamp (DTS).
392 pub fn dts(mut self, dts: Timestamp) -> Self {
393 self.dts = Some(dts);
394 self
395 }
396
397 /// Provide the trim start duration.
398 pub fn trim_start(mut self, trim_start: Duration) -> Self {
399 self.trim_start = trim_start;
400 self
401 }
402
403 /// Provide the trim end duration.
404 pub fn trim_end(mut self, trim_end: Duration) -> Self {
405 self.trim_end = trim_end;
406 self
407 }
408 }
409}
410
411pub use builder::PacketBuilder;
412
413#[cfg(test)]
414mod tests {
415 use super::PacketBuilder;
416 use crate::units::{Duration, Timestamp};
417
418 #[test]
419 fn verify_packet_ref_creation() {
420 let data: &[u8] = &[1, 2, 3, 4];
421
422 let pkt_ref = PacketBuilder::new()
423 .track_id(1)
424 .pts(Timestamp::new(100))
425 .dts(Timestamp::new(90))
426 .dur(Duration::new(50))
427 .data_by_ref(data)
428 .trim_start(Duration::new(10))
429 .trim_end(Duration::new(5))
430 .build_packet_ref();
431
432 assert_eq!(pkt_ref.track_id, 1);
433 assert_eq!(pkt_ref.pts, Timestamp::new(100));
434 assert_eq!(pkt_ref.dts, Timestamp::new(90));
435 assert_eq!(pkt_ref.dur, Duration::new(50));
436 assert_eq!(pkt_ref.trim_start, Duration::new(10));
437 assert_eq!(pkt_ref.trim_end, Duration::new(5));
438 assert_eq!(&pkt_ref.data, &[1, 2, 3, 4]);
439
440 // block_dur = dur + trim_start + trim_end = 50 + 10 + 5 = 65
441 assert_eq!(pkt_ref.block_dur(), Duration::new(65));
442 }
443
444 #[test]
445 fn verify_packet_to_packet_ref() {
446 let data = vec![5, 6, 7, 8];
447
448 let pkt = PacketBuilder::new()
449 .track_id(2)
450 .pts(Timestamp::new(200))
451 .dur(Duration::new(100))
452 .data(data)
453 .dts(Timestamp::new(190))
454 .trim_start(Duration::new(20))
455 .trim_end(Duration::new(10))
456 .build();
457
458 let pkt_ref = pkt.as_packet_ref();
459
460 assert_eq!(pkt_ref.track_id, 2);
461 assert_eq!(pkt_ref.pts, Timestamp::new(200));
462 assert_eq!(pkt_ref.dts, Timestamp::new(190));
463 assert_eq!(pkt_ref.dur, Duration::new(100));
464 assert_eq!(pkt_ref.trim_start, Duration::new(20));
465 assert_eq!(pkt_ref.trim_end, Duration::new(10));
466 assert_eq!(&pkt_ref.data, &[5, 6, 7, 8]);
467 }
468}