Skip to main content

zencodec_media/
packet.rs

1//! Compressed access units.
2//!
3//! A [`Packet`] is what a demuxer yields and a decoder takes, and what an
4//! encoder yields and a muxer takes. It is never a picture: a decoder may
5//! produce zero, one or several frames per packet, and may produce them
6//! later (reordering, hidden frames, show-existing).
7//!
8//! The payload is a [`PacketPayload`]: an owned `Vec`, or a range of a shared
9//! buffer, so a demuxer can hand out slices of one read without copying and
10//! a backend can take an owned `Vec` without copying either. A consumer that
11//! accepts a packet takes its payload ([`Packet::take`]); one that pushes
12//! back leaves it in place for the retry.
13
14use alloc::sync::Arc;
15use alloc::vec::Vec;
16use core::fmt;
17
18use zencodec::time::{FrameDuration, Timestamp};
19
20/// Where a packet's bytes live.
21#[derive(Clone, Debug)]
22#[non_exhaustive]
23pub enum PacketPayload {
24    /// Bytes the packet owns.
25    Owned(Vec<u8>),
26    /// `len` bytes of `bytes` starting at `start`.
27    Shared {
28        /// The shared buffer.
29        bytes: Arc<[u8]>,
30        /// First byte of the payload.
31        start: usize,
32        /// Payload length.
33        len: usize,
34    },
35    /// Admitted storage. Clones and subviews retain the backing allocation's
36    /// charge until its last owner drops.
37    Accounted(zencodec_io::storage::SharedStorage<u8>),
38}
39
40impl PacketPayload {
41    /// The payload bytes.
42    pub fn as_bytes(&self) -> &[u8] {
43        match self {
44            Self::Owned(v) => v,
45            Self::Shared { bytes, start, len } => &bytes[*start..*start + *len],
46            Self::Accounted(bytes) => bytes.as_slice(),
47        }
48    }
49
50    /// Payload length in bytes.
51    pub fn len(&self) -> usize {
52        self.as_bytes().len()
53    }
54
55    /// Whether the payload is empty.
56    pub fn is_empty(&self) -> bool {
57        self.len() == 0
58    }
59
60    /// The payload as an ordinary, unaccounted vector: free for `Owned`, a copy
61    /// for `Shared` and `Accounted`. The copy is outside a storage budget.
62    /// Budget-enforcing adapters keep the accounted payload instead of calling
63    /// this legacy ownership bridge.
64    pub fn into_vec(self) -> Vec<u8> {
65        match self {
66            Self::Owned(v) => v,
67            Self::Shared { .. } | Self::Accounted(_) => self.as_bytes().to_vec(),
68        }
69    }
70}
71
72/// What the container says about random access at a packet.
73///
74/// This is container evidence (an MP4 sync sample, a WebM `SimpleBlock`
75/// keyframe flag, a cue point), not a codec's keyframe flag: an AV1 key
76/// frame that needs an earlier sequence header, or one shown later through
77/// show-existing, is not a random-access point by itself. `Unknown` is
78/// common (IVF records nothing) and never treated as `Yes`.
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
80pub enum RandomAccess {
81    /// Decoding can start here.
82    Yes,
83    /// Decoding cannot start here.
84    No,
85    /// The container does not say.
86    #[default]
87    Unknown,
88}
89
90/// Side data carried with one packet: a WebM `BlockAdditional`, an alpha
91/// plane coded separately. How additions pair with the main payload is the
92/// codec's business, not the packet's.
93#[derive(Clone, Debug)]
94pub struct PacketAddition {
95    id: u32,
96    payload: PacketPayload,
97}
98
99impl PacketAddition {
100    /// Side data with the container's identifier.
101    pub fn new(id: u32, payload: PacketPayload) -> Self {
102        Self { id, payload }
103    }
104
105    /// The container's identifier for this kind of side data.
106    pub fn id(&self) -> u32 {
107        self.id
108    }
109
110    /// The side data.
111    pub fn payload(&self) -> &PacketPayload {
112        &self.payload
113    }
114}
115
116/// Decoded media to drop from one packet's output: padding the encoder added
117/// (WebM `DiscardPadding`, the end of an Opus or AAC stream).
118///
119/// Carried untouched by demuxers, muxers and stream copies, and applied
120/// once, by the decoder adapter configured from the track. A container that
121/// can record only one side (WebM: a positive `DiscardPadding` trims the
122/// end, a negative one the start) refuses a packet that needs both rather
123/// than dropping one.
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125#[non_exhaustive]
126pub struct Discard {
127    /// Media to drop from the start of the packet's output.
128    pub start: Option<FrameDuration>,
129    /// Media to drop from the end of the packet's output.
130    pub end: Option<FrameDuration>,
131}
132
133impl Discard {
134    /// Drop `d` from the start of the packet's output.
135    pub const fn start(d: FrameDuration) -> Self {
136        Self {
137            start: Some(d),
138            end: None,
139        }
140    }
141
142    /// Drop `d` from the end of the packet's output.
143    pub const fn end(d: FrameDuration) -> Self {
144        Self {
145            start: None,
146            end: Some(d),
147        }
148    }
149
150    /// Also drop `d` from the end.
151    pub const fn with_end(mut self, d: FrameDuration) -> Self {
152        self.end = Some(d);
153        self
154    }
155}
156
157/// A compressed access unit and what the container recorded about it.
158///
159/// Every timing field is optional, and a missing value stays missing:
160/// nothing in this crate derives a timestamp from a nominal frame rate. A
161/// timestamp uses the track's [`TimeBase`](zencodec::time::TimeBase).
162#[derive(Clone, Debug)]
163pub struct Packet {
164    payload: Option<PacketPayload>,
165    track: u32,
166    pts: Option<Timestamp>,
167    dts: Option<Timestamp>,
168    duration_ticks: Option<u64>,
169    random_access: RandomAccess,
170    config_epoch: u32,
171    ordinal: Option<u64>,
172    input_offset: Option<u64>,
173    discard: Option<Discard>,
174    additions: Vec<PacketAddition>,
175}
176
177/// Invalid packet construction.
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179#[non_exhaustive]
180pub enum PacketError {
181    /// A packet must carry at least one byte.
182    Empty,
183    /// The shared range lies outside its buffer.
184    OutOfRange,
185}
186
187impl fmt::Display for PacketError {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.write_str(match self {
190            Self::Empty => "packet payload is empty",
191            Self::OutOfRange => "shared payload range is outside its buffer",
192        })
193    }
194}
195
196impl core::error::Error for PacketError {}
197
198impl Packet {
199    #[cfg(feature = "std")]
200    pub(crate) fn map_payloads(
201        mut self,
202        mut map: impl FnMut(&PacketPayload) -> Result<PacketPayload, crate::staged::Cause>,
203    ) -> Result<Self, crate::staged::Cause> {
204        if let Some(payload) = &self.payload {
205            self.payload = Some(map(payload)?);
206        }
207        for addition in &mut self.additions {
208            addition.payload = map(&addition.payload)?;
209        }
210        Ok(self)
211    }
212
213    #[cfg(feature = "std")]
214    pub(crate) fn accounted(
215        mut self,
216        budget: &zencodec::storage::StorageBudget,
217    ) -> Result<Self, zencodec::storage::StorageError> {
218        let copy = |source: &[u8]| {
219            let mut bytes = budget.bytes(source.len())?;
220            bytes.as_mut_slice().copy_from_slice(source);
221            Ok::<_, zencodec::storage::StorageError>(PacketPayload::Accounted(bytes.freeze()))
222        };
223        if let Some(payload) = &self.payload {
224            self.payload = Some(copy(payload.as_bytes())?);
225        }
226        for addition in &mut self.additions {
227            addition.payload = copy(addition.payload.as_bytes())?;
228        }
229        Ok(self)
230    }
231
232    fn with_payload(payload: PacketPayload) -> Self {
233        Self {
234            payload: Some(payload),
235            track: 0,
236            pts: None,
237            dts: None,
238            duration_ticks: None,
239            random_access: RandomAccess::Unknown,
240            config_epoch: 0,
241            ordinal: None,
242            input_offset: None,
243            discard: None,
244            additions: Vec::new(),
245        }
246    }
247
248    /// A packet owning `bytes`, on track 0 with nothing else recorded.
249    ///
250    /// # Errors
251    ///
252    /// [`PacketError::Empty`] for an empty payload.
253    pub fn from_vec(bytes: Vec<u8>) -> Result<Self, PacketError> {
254        if bytes.is_empty() {
255            return Err(PacketError::Empty);
256        }
257        Ok(Self::with_payload(PacketPayload::Owned(bytes)))
258    }
259
260    /// A packet over already admitted storage, without copying or releasing
261    /// the charge. Packet clones and `take()` retain the same allocation owner.
262    pub fn from_accounted(
263        bytes: zencodec_io::storage::SharedStorage<u8>,
264    ) -> Result<Self, PacketError> {
265        if bytes.is_empty() {
266            return Err(PacketError::Empty);
267        }
268        Ok(Self::with_payload(PacketPayload::Accounted(bytes)))
269    }
270
271    /// A packet over `len` bytes of `bytes` from `start`, without copying.
272    ///
273    /// # Errors
274    ///
275    /// [`PacketError::Empty`] for `len == 0`; [`PacketError::OutOfRange`]
276    /// when the range leaves the buffer.
277    pub fn from_shared(bytes: Arc<[u8]>, start: usize, len: usize) -> Result<Self, PacketError> {
278        if len == 0 {
279            return Err(PacketError::Empty);
280        }
281        if start.checked_add(len).is_none_or(|end| end > bytes.len()) {
282            return Err(PacketError::OutOfRange);
283        }
284        Ok(Self::with_payload(PacketPayload::Shared {
285            bytes,
286            start,
287            len,
288        }))
289    }
290
291    /// The track this packet belongs to: an index into the demuxer's
292    /// [`tracks`](crate::container::Demuxer::tracks), or into what a muxer
293    /// got at `begin`.
294    pub fn with_track(mut self, track: u32) -> Self {
295        self.track = track;
296        self
297    }
298
299    /// The presentation timestamp.
300    pub fn with_pts(mut self, pts: Timestamp) -> Self {
301        self.pts = Some(pts);
302        self
303    }
304
305    /// The decode timestamp, when the container stores one apart from the
306    /// presentation timestamp.
307    pub fn with_dts(mut self, dts: Timestamp) -> Self {
308        self.dts = Some(dts);
309        self
310    }
311
312    /// The duration, in ticks of the track's time base, when the container
313    /// records one.
314    pub fn with_duration_ticks(mut self, ticks: u64) -> Self {
315        self.duration_ticks = Some(ticks);
316        self
317    }
318
319    /// The container's random-access evidence.
320    pub fn with_random_access(mut self, ra: RandomAccess) -> Self {
321        self.random_access = ra;
322        self
323    }
324
325    /// The codec configuration epoch this packet was coded under.
326    pub fn with_config_epoch(mut self, epoch: u32) -> Self {
327        self.config_epoch = epoch;
328        self
329    }
330
331    /// The packet's position in the track's packet order.
332    pub fn with_ordinal(mut self, ordinal: u64) -> Self {
333        self.ordinal = Some(ordinal);
334        self
335    }
336
337    /// Where the payload starts in the container file.
338    pub fn with_input_offset(mut self, offset: u64) -> Self {
339        self.input_offset = Some(offset);
340        self
341    }
342
343    /// Media the decoder adapter drops from this packet's output.
344    pub fn with_discard(mut self, discard: Discard) -> Self {
345        self.discard = Some(discard);
346        self
347    }
348
349    /// Attach side data.
350    pub fn with_addition(mut self, addition: PacketAddition) -> Self {
351        self.additions.push(addition);
352        self
353    }
354
355    /// Payload bytes; empty once an accepting consumer took them.
356    pub fn bytes(&self) -> &[u8] {
357        self.payload.as_ref().map_or(&[], PacketPayload::as_bytes)
358    }
359
360    /// Payload length; zero once taken.
361    pub fn len(&self) -> usize {
362        self.bytes().len()
363    }
364
365    /// Whether no payload bytes are left (empty only once taken).
366    pub fn is_empty(&self) -> bool {
367        self.len() == 0
368    }
369
370    /// Whether an accepting consumer took the payload.
371    pub fn is_taken(&self) -> bool {
372        self.payload.is_none()
373    }
374
375    /// Take the payload: what a consumer does when it accepts the packet.
376    /// The metadata stays readable.
377    pub fn take(&mut self) -> Option<PacketPayload> {
378        self.payload.take()
379    }
380
381    /// The track this packet belongs to.
382    pub fn track(&self) -> u32 {
383        self.track
384    }
385
386    /// The presentation timestamp, if recorded.
387    pub fn pts(&self) -> Option<Timestamp> {
388        self.pts
389    }
390
391    /// The decode timestamp, if recorded apart from the presentation one.
392    pub fn dts(&self) -> Option<Timestamp> {
393        self.dts
394    }
395
396    /// Decode-order time: `dts` when the container stores one, else `pts`.
397    pub fn decode_time(&self) -> Option<Timestamp> {
398        self.dts.or(self.pts)
399    }
400
401    /// The recorded duration in ticks of the track's time base.
402    pub fn duration_ticks(&self) -> Option<u64> {
403        self.duration_ticks
404    }
405
406    /// The container's random-access evidence.
407    pub fn random_access(&self) -> RandomAccess {
408        self.random_access
409    }
410
411    /// The configuration epoch this packet was coded under.
412    pub fn config_epoch(&self) -> u32 {
413        self.config_epoch
414    }
415
416    /// The packet's position in its track's packet order.
417    pub fn ordinal(&self) -> Option<u64> {
418        self.ordinal
419    }
420
421    /// Where the payload starts in the container file.
422    pub fn input_offset(&self) -> Option<u64> {
423        self.input_offset
424    }
425
426    /// Media to drop from this packet's output, as the container recorded it.
427    pub fn discard(&self) -> Option<Discard> {
428        self.discard
429    }
430
431    /// Side data carried with the packet.
432    pub fn additions(&self) -> &[PacketAddition] {
433        &self.additions
434    }
435}
436
437#[cfg(all(test, feature = "std"))]
438mod accounted_tests {
439    use super::*;
440    use alloc::vec;
441    use zencodec::{storage::StorageBudget, time::TimeBase};
442    #[test]
443    fn staging_preserves_packet_and_additions_and_releases_partial_admission() {
444        let original = Packet::from_vec(vec![1, 2, 3])
445            .unwrap()
446            .with_track(7)
447            .with_pts(Timestamp::new(19, TimeBase::MILLIS))
448            .with_dts(Timestamp::new(17, TimeBase::MILLIS))
449            .with_duration_ticks(2)
450            .with_config_epoch(3)
451            .with_ordinal(9)
452            .with_input_offset(123)
453            .with_random_access(RandomAccess::Yes)
454            .with_addition(PacketAddition::new(6, PacketPayload::Owned(vec![4, 5])));
455        let small = StorageBudget::new(4, 8);
456        assert!(original.clone().accounted(&small).is_err());
457        assert_eq!(small.reserved_bytes(), 0);
458        assert_eq!(small.reserved_objects(), 0);
459        let budget = StorageBudget::new(5, 2);
460        let output = original.clone().accounted(&budget).unwrap();
461        assert_eq!(output.bytes(), original.bytes());
462        assert_eq!(output.track(), original.track());
463        assert_eq!(output.pts(), original.pts());
464        assert_eq!(output.dts(), original.dts());
465        assert_eq!(output.duration_ticks(), original.duration_ticks());
466        assert_eq!(output.config_epoch(), original.config_epoch());
467        assert_eq!(output.ordinal(), original.ordinal());
468        assert_eq!(output.input_offset(), original.input_offset());
469        assert_eq!(output.random_access(), original.random_access());
470        assert_eq!(output.additions()[0].id(), 6);
471        assert_eq!(output.additions()[0].payload().as_bytes(), &[4, 5]);
472        assert_eq!(budget.reserved_bytes(), 5);
473        let retained = output.clone();
474        drop(output);
475        assert_eq!(budget.reserved_bytes(), 5);
476        drop(retained);
477        assert_eq!(budget.reserved_bytes(), 0);
478    }
479}