Skip to main content

transmux/
sample_groups.rs

1//! ProducerReferenceTimeBox, SampleGroupDescriptionBox, SampleToGroupBox, and
2//! SubSampleInformationBox — ISO/IEC 14496-12:2015 §8.16.5, §8.9.2, §8.9.3,
3//! §8.7.7.
4//!
5//! Typed containers for:
6//!
7//! | Box   | Name                           | Section   | Description                                       |
8//! |-------|--------------------------------|-----------|---------------------------------------------------|
9//! | `prft`| ProducerReferenceTimeBox       | §8.16.5   | NTP wall-clock anchor for the reference track     |
10//! | `sgpd`| SampleGroupDescriptionBox      | §8.9.3    | Per-grouping-type sample group description table  |
11//! | `sbgp`| SampleToGroupBox               | §8.9.2    | Maps samples to sample group description indices  |
12//! | `subs`| SubSampleInformationBox        | §8.7.7    | Per-sample sub-sample size/priority table         |
13//!
14//! All boxes are FullBoxes. Sizes are computed from fields; no `self.raw`
15//! passthrough in any serializer.
16//!
17//! # Spec citations
18//!
19//! - **prft**: ISO/IEC 14496-12:2015 §8.16.5.2 — version 0: `media_time` is u32;
20//!   version 1: `media_time` is u64.
21//! - **sgpd**: ISO/IEC 14496-12:2015 §8.9.3.2 — version 1: `default_length` field
22//!   present; version 0 is deprecated. Only `'roll'` (RollRecoveryEntry, i16
23//!   `roll_distance`) is typed; all other grouping types are stored as raw bytes
24//!   via [`SgpdEntry::Unknown`].
25//! - **sbgp**: ISO/IEC 14496-12:2015 §8.9.2.2 — version 0: no
26//!   `grouping_type_parameter`; version 1: `grouping_type_parameter` present.
27//! - **subs**: ISO/IEC 14496-12:2015 §8.7.7.2 — version 0: `subsample_size` is
28//!   u16; version 1: `subsample_size` is u32.
29
30use crate::error::{Error, Result};
31use alloc::vec::Vec;
32
33use broadcast_common::{Parse, Serialize};
34
35// ---------------------------------------------------------------------------
36// Wire-layout constants
37// ---------------------------------------------------------------------------
38
39const BOX_HEADER_SIZE: usize = 8;
40const FULLBOX_EXTRA_SIZE: usize = 4;
41
42const PRFT_TYPE: u32 = u32::from_be_bytes(*b"prft");
43const SGPD_TYPE: u32 = u32::from_be_bytes(*b"sgpd");
44const SBGP_TYPE: u32 = u32::from_be_bytes(*b"sbgp");
45const SUBS_TYPE: u32 = u32::from_be_bytes(*b"subs");
46
47/// Grouping type `'roll'` (RollRecoveryEntry) — ISO/IEC 14496-12:2015 §10.6.
48pub const GROUPING_TYPE_ROLL: u32 = u32::from_be_bytes(*b"roll");
49
50// ---------------------------------------------------------------------------
51// ProducerReferenceTimeBox — prft (ISO/IEC 14496-12:2015 §8.16.5)
52// ---------------------------------------------------------------------------
53
54/// Producer Reference Time Box (`prft`) — ISO/IEC 14496-12:2015 §8.16.5.2.
55///
56/// Provides a UTC wall-clock anchor for the reference track.
57///
58/// Wire layout (FullBox header omitted):
59///
60/// ```text
61/// reference_track_ID   u(32)
62/// ntp_timestamp        u(64)  — UTC time in NTP format
63/// media_time           u(32) if version == 0
64///                      u(64) if version == 1
65/// ```
66///
67/// `reference_track_ID` identifies the track whose decoding timeline is anchored.
68/// `ntp_timestamp` is the wall-clock time in NTP format corresponding to
69/// `media_time`. `media_time` is in the timescale of the reference track.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize))]
72pub struct ProducerReferenceTimeBox {
73    /// FullBox version: 0 = `media_time` is u32; 1 = `media_time` is u64.
74    pub version: u8,
75    /// FullBox flags `[23:0]`.
76    pub flags: u32,
77    /// Track ID of the reference track (§8.16.5.3).
78    pub reference_track_id: u32,
79    /// UTC time in NTP format (§8.16.5.3).
80    pub ntp_timestamp: u64,
81    /// Media time in the reference track's timescale.
82    ///
83    /// Stored as u64; for version 0 the upper 32 bits are zero on the wire.
84    pub media_time: u64,
85}
86
87impl ProducerReferenceTimeBox {
88    /// Parse the body of a `prft` box (after the 8-byte BoxHeader).
89    pub fn parse_body(body: &[u8]) -> Result<Self> {
90        // version(1) + flags(3) + ref_track_id(4) + ntp(8) = 16 minimum
91        let min = FULLBOX_EXTRA_SIZE + 4 + 8;
92        if body.len() < min {
93            return Err(Error::BufferTooShort {
94                need: min,
95                have: body.len(),
96                what: "prft body",
97            });
98        }
99        let version = body[0];
100        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
101        let mut c = FULLBOX_EXTRA_SIZE;
102        let reference_track_id =
103            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
104        c += 4;
105        let ntp_timestamp = u64::from_be_bytes([
106            body[c],
107            body[c + 1],
108            body[c + 2],
109            body[c + 3],
110            body[c + 4],
111            body[c + 5],
112            body[c + 6],
113            body[c + 7],
114        ]);
115        c += 8;
116        let media_time = if version == 0 {
117            if body.len() < c + 4 {
118                return Err(Error::BufferTooShort {
119                    need: c + 4,
120                    have: body.len(),
121                    what: "prft media_time v0",
122                });
123            }
124            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as u64
125        } else {
126            if body.len() < c + 8 {
127                return Err(Error::BufferTooShort {
128                    need: c + 8,
129                    have: body.len(),
130                    what: "prft media_time v1",
131                });
132            }
133            u64::from_be_bytes([
134                body[c],
135                body[c + 1],
136                body[c + 2],
137                body[c + 3],
138                body[c + 4],
139                body[c + 5],
140                body[c + 6],
141                body[c + 7],
142            ])
143        };
144        Ok(Self {
145            version,
146            flags,
147            reference_track_id,
148            ntp_timestamp,
149            media_time,
150        })
151    }
152}
153
154impl<'a> Parse<'a> for ProducerReferenceTimeBox {
155    type Error = Error;
156    fn parse(bytes: &'a [u8]) -> Result<Self> {
157        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + 4 {
158            return Err(Error::BufferTooShort {
159                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + 4,
160                have: bytes.len(),
161                what: "prft box",
162            });
163        }
164        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
165        if ty != PRFT_TYPE {
166            return Err(Error::InvalidValue {
167                field: "box_type",
168                value: ty as u64,
169                reason: "expected prft",
170            });
171        }
172        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
173    }
174}
175
176impl Serialize for ProducerReferenceTimeBox {
177    type Error = Error;
178    fn serialized_len(&self) -> usize {
179        let mt_size = if self.version == 0 { 4 } else { 8 };
180        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + 8 + mt_size
181    }
182    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
183        let need = self.serialized_len();
184        if buf.len() < need {
185            return Err(Error::OutputBufferTooSmall {
186                need,
187                have: buf.len(),
188            });
189        }
190        let mut c = 0;
191        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
192        c += 4;
193        buf[c..c + 4].copy_from_slice(b"prft");
194        c += 4;
195        buf[c] = self.version;
196        let fb = self.flags.to_be_bytes();
197        buf[c + 1] = fb[1];
198        buf[c + 2] = fb[2];
199        buf[c + 3] = fb[3];
200        c += 4;
201        buf[c..c + 4].copy_from_slice(&self.reference_track_id.to_be_bytes());
202        c += 4;
203        buf[c..c + 8].copy_from_slice(&self.ntp_timestamp.to_be_bytes());
204        c += 8;
205        if self.version == 0 {
206            buf[c..c + 4].copy_from_slice(&(self.media_time as u32).to_be_bytes());
207            c += 4;
208        } else {
209            buf[c..c + 8].copy_from_slice(&self.media_time.to_be_bytes());
210            c += 8;
211        }
212        Ok(c)
213    }
214}
215
216// ---------------------------------------------------------------------------
217// SampleGroupDescriptionBox — sgpd (ISO/IEC 14496-12:2015 §8.9.3)
218// ---------------------------------------------------------------------------
219
220/// A parsed entry in the sgpd sample group description table.
221///
222/// Only `'roll'` (§10.6 RollRecoveryEntry) is fully typed; all other grouping
223/// types carry their body as raw bytes in [`SgpdEntry::Unknown`].
224#[derive(Debug, Clone, PartialEq, Eq)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize))]
226#[non_exhaustive]
227pub enum SgpdEntry {
228    /// RollRecoveryEntry for grouping type `'roll'` (§10.6).
229    ///
230    /// `roll_distance` is the number of samples that must be decoded before the
231    /// stream is usable (negative = pre-roll, positive = post-roll).
232    Roll {
233        /// Roll distance in samples. Negative = pre-roll.
234        roll_distance: i16,
235    },
236    /// Raw bytes for any grouping type not specifically handled.
237    Unknown(Vec<u8>),
238}
239
240impl SgpdEntry {
241    /// Serialized size of this entry on the wire (body bytes only, no length prefix).
242    pub fn wire_len(&self) -> usize {
243        match self {
244            Self::Roll { .. } => 2,
245            Self::Unknown(v) => v.len(),
246        }
247    }
248}
249
250/// Sample Group Description Box (`sgpd`) — ISO/IEC 14496-12:2015 §8.9.3.2.
251///
252/// Version 1 is the current (non-deprecated) form and is the only version
253/// emitted by this serializer. Version 0 can be parsed.
254///
255/// Wire layout (FullBox header omitted):
256///
257/// ```text
258/// grouping_type          u(32)
259/// default_length         u(32)  — version == 1 only
260/// entry_count            u(32)
261/// for each entry:
262///   [description_length  u(32)] — only if version == 1 && default_length == 0
263///   SampleGroupEntry (grouping_type)
264/// ```
265///
266/// When `version == 1` and `default_length != 0`, every entry has the same
267/// length (`default_length` bytes). When `default_length == 0`, each entry is
268/// preceded by a 4-byte `description_length`.
269#[derive(Debug, Clone, PartialEq, Eq)]
270#[cfg_attr(feature = "serde", derive(serde::Serialize))]
271pub struct SampleGroupDescriptionBox {
272    /// FullBox version.
273    pub version: u8,
274    /// FullBox flags `[23:0]`.
275    pub flags: u32,
276    /// Four-CC grouping type (e.g. `GROUPING_TYPE_ROLL`).
277    pub grouping_type: u32,
278    /// `default_length` from the v1 syntax; 0 means variable-length entries.
279    ///
280    /// On serialization this is recomputed from entries: if all entries have the
281    /// same `wire_len`, `default_length` is set to that value; otherwise 0
282    /// (each entry gets an explicit `description_length` prefix).
283    pub default_length: u32,
284    /// Parsed entries.
285    pub entries: Vec<SgpdEntry>,
286}
287
288impl SampleGroupDescriptionBox {
289    /// Parse the body of an `sgpd` box (after the 8-byte BoxHeader).
290    pub fn parse_body(body: &[u8]) -> Result<Self> {
291        if body.len() < FULLBOX_EXTRA_SIZE + 4 + 4 {
292            return Err(Error::BufferTooShort {
293                need: FULLBOX_EXTRA_SIZE + 4 + 4,
294                have: body.len(),
295                what: "sgpd body",
296            });
297        }
298        let version = body[0];
299        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
300        let mut c = FULLBOX_EXTRA_SIZE;
301        let grouping_type = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
302        c += 4;
303
304        let default_length = if version == 1 {
305            if body.len() < c + 4 {
306                return Err(Error::BufferTooShort {
307                    need: c + 4,
308                    have: body.len(),
309                    what: "sgpd default_length",
310                });
311            }
312            let dl = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
313            c += 4;
314            dl
315        } else if version >= 2 {
316            // version 2 has default_sample_description_index instead
317            if body.len() < c + 4 {
318                return Err(Error::BufferTooShort {
319                    need: c + 4,
320                    have: body.len(),
321                    what: "sgpd default_sample_description_index",
322                });
323            }
324            c += 4; // skip default_sample_description_index
325            0
326        } else {
327            0 // version 0: no default_length field
328        };
329
330        if body.len() < c + 4 {
331            return Err(Error::BufferTooShort {
332                need: c + 4,
333                have: body.len(),
334                what: "sgpd entry_count",
335            });
336        }
337        let entry_count =
338            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
339        c += 4;
340
341        let mut entries = Vec::with_capacity(entry_count);
342        for _ in 0..entry_count {
343            // Determine entry length
344            let entry_len: usize = if version == 1 && default_length == 0 {
345                if body.len() < c + 4 {
346                    return Err(Error::BufferTooShort {
347                        need: c + 4,
348                        have: body.len(),
349                        what: "sgpd description_length",
350                    });
351                }
352                let dl =
353                    u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
354                c += 4;
355                dl
356            } else if version == 1 {
357                default_length as usize
358            } else {
359                // version 0: entry size is implied by the grouping_type; we
360                // parse until we hit the known size for 'roll' (2 bytes), else
361                // we consume remaining bytes as one blob (rare / deprecated).
362                if grouping_type == GROUPING_TYPE_ROLL {
363                    2
364                } else {
365                    // unknown v0: consume all remaining as one entry
366                    body.len() - c
367                }
368            };
369            if body.len() < c + entry_len {
370                return Err(Error::BufferTooShort {
371                    need: c + entry_len,
372                    have: body.len(),
373                    what: "sgpd entry body",
374                });
375            }
376            let entry_bytes = &body[c..c + entry_len];
377            let entry = if grouping_type == GROUPING_TYPE_ROLL && entry_len >= 2 {
378                let rd = i16::from_be_bytes([entry_bytes[0], entry_bytes[1]]);
379                SgpdEntry::Roll { roll_distance: rd }
380            } else {
381                SgpdEntry::Unknown(entry_bytes.to_vec())
382            };
383            entries.push(entry);
384            c += entry_len;
385        }
386
387        Ok(Self {
388            version,
389            flags,
390            grouping_type,
391            default_length,
392            entries,
393        })
394    }
395}
396
397impl<'a> Parse<'a> for SampleGroupDescriptionBox {
398    type Error = Error;
399    fn parse(bytes: &'a [u8]) -> Result<Self> {
400        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
401            return Err(Error::BufferTooShort {
402                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
403                have: bytes.len(),
404                what: "sgpd box",
405            });
406        }
407        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
408        if ty != SGPD_TYPE {
409            return Err(Error::InvalidValue {
410                field: "box_type",
411                value: ty as u64,
412                reason: "expected sgpd",
413            });
414        }
415        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
416    }
417}
418
419impl Serialize for SampleGroupDescriptionBox {
420    type Error = Error;
421    fn serialized_len(&self) -> usize {
422        // Compute whether we will use a uniform default_length.
423        let (use_default_len, per_entry_prefix) = self.effective_default_length();
424        let entry_overhead = if per_entry_prefix { 4 } else { 0 };
425        let entries_size: usize = self
426            .entries
427            .iter()
428            .map(|e| entry_overhead + e.wire_len())
429            .sum();
430        // header + fullbox + grouping_type + [default_length] + entry_count + entries
431        let dl_field = if self.version == 1 { 4 } else { 0 };
432        let _ = use_default_len;
433        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + dl_field + 4 + entries_size
434    }
435
436    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
437        let need = self.serialized_len();
438        if buf.len() < need {
439            return Err(Error::OutputBufferTooSmall {
440                need,
441                have: buf.len(),
442            });
443        }
444        let (effective_dl, per_entry_prefix) = self.effective_default_length();
445        let mut c = 0;
446        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
447        c += 4;
448        buf[c..c + 4].copy_from_slice(b"sgpd");
449        c += 4;
450        buf[c] = self.version;
451        let fb = self.flags.to_be_bytes();
452        buf[c + 1] = fb[1];
453        buf[c + 2] = fb[2];
454        buf[c + 3] = fb[3];
455        c += 4;
456        buf[c..c + 4].copy_from_slice(&self.grouping_type.to_be_bytes());
457        c += 4;
458        if self.version == 1 {
459            buf[c..c + 4].copy_from_slice(&effective_dl.to_be_bytes());
460            c += 4;
461        }
462        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
463        c += 4;
464        for entry in &self.entries {
465            if per_entry_prefix {
466                buf[c..c + 4].copy_from_slice(&(entry.wire_len() as u32).to_be_bytes());
467                c += 4;
468            }
469            match entry {
470                SgpdEntry::Roll { roll_distance } => {
471                    buf[c..c + 2].copy_from_slice(&roll_distance.to_be_bytes());
472                    c += 2;
473                }
474                SgpdEntry::Unknown(v) => {
475                    buf[c..c + v.len()].copy_from_slice(v);
476                    c += v.len();
477                }
478            }
479        }
480        Ok(c)
481    }
482}
483
484impl SampleGroupDescriptionBox {
485    /// Compute the `default_length` to write and whether per-entry length
486    /// prefixes are needed.
487    ///
488    /// Returns `(effective_default_length, per_entry_prefix_needed)`.
489    fn effective_default_length(&self) -> (u32, bool) {
490        if self.version != 1 || self.entries.is_empty() {
491            return (0, false);
492        }
493        let first = self.entries[0].wire_len();
494        let uniform = self.entries.iter().all(|e| e.wire_len() == first);
495        if uniform {
496            (first as u32, false)
497        } else {
498            (0, true)
499        }
500    }
501}
502
503// ---------------------------------------------------------------------------
504// SampleToGroupBox — sbgp (ISO/IEC 14496-12:2015 §8.9.2)
505// ---------------------------------------------------------------------------
506
507/// Entry in the sbgp sample-to-group mapping table (§8.9.2.2).
508#[derive(Debug, Clone, Copy, PartialEq, Eq)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize))]
510pub struct SbgpEntry {
511    /// Number of consecutive samples belonging to this group.
512    pub sample_count: u32,
513    /// Index into the sgpd table (1-based, or 0 = no group).
514    pub group_description_index: u32,
515}
516
517/// Sample To Group Box (`sbgp`) — ISO/IEC 14496-12:2015 §8.9.2.2.
518///
519/// Wire layout (FullBox header omitted):
520///
521/// ```text
522/// grouping_type            u(32)
523/// [grouping_type_parameter u(32)] — version == 1 only
524/// entry_count              u(32)
525/// for each entry:
526///   sample_count             u(32)
527///   group_description_index  u(32)
528/// ```
529#[derive(Debug, Clone, PartialEq, Eq)]
530#[cfg_attr(feature = "serde", derive(serde::Serialize))]
531pub struct SampleToGroupBox {
532    /// FullBox version: 0 = no `grouping_type_parameter`; 1 = has it.
533    pub version: u8,
534    /// FullBox flags `[23:0]`.
535    pub flags: u32,
536    /// Four-CC grouping type linking this box to its sgpd.
537    pub grouping_type: u32,
538    /// Optional sub-type parameter (version 1 only).
539    pub grouping_type_parameter: Option<u32>,
540    /// Mapping entries.
541    pub entries: Vec<SbgpEntry>,
542}
543
544impl SampleToGroupBox {
545    /// Parse the body of an `sbgp` box (after the 8-byte BoxHeader).
546    pub fn parse_body(body: &[u8]) -> Result<Self> {
547        if body.len() < FULLBOX_EXTRA_SIZE + 4 + 4 {
548            return Err(Error::BufferTooShort {
549                need: FULLBOX_EXTRA_SIZE + 4 + 4,
550                have: body.len(),
551                what: "sbgp body",
552            });
553        }
554        let version = body[0];
555        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
556        let mut c = FULLBOX_EXTRA_SIZE;
557        let grouping_type = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
558        c += 4;
559        let grouping_type_parameter = if version == 1 {
560            if body.len() < c + 4 {
561                return Err(Error::BufferTooShort {
562                    need: c + 4,
563                    have: body.len(),
564                    what: "sbgp grouping_type_parameter",
565                });
566            }
567            let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
568            c += 4;
569            Some(v)
570        } else {
571            None
572        };
573        if body.len() < c + 4 {
574            return Err(Error::BufferTooShort {
575                need: c + 4,
576                have: body.len(),
577                what: "sbgp entry_count",
578            });
579        }
580        let entry_count =
581            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
582        c += 4;
583        let mut entries = Vec::with_capacity(entry_count);
584        for _ in 0..entry_count {
585            if body.len() < c + 8 {
586                return Err(Error::BufferTooShort {
587                    need: c + 8,
588                    have: body.len(),
589                    what: "sbgp entry",
590                });
591            }
592            let sample_count = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
593            let group_description_index =
594                u32::from_be_bytes([body[c + 4], body[c + 5], body[c + 6], body[c + 7]]);
595            entries.push(SbgpEntry {
596                sample_count,
597                group_description_index,
598            });
599            c += 8;
600        }
601        Ok(Self {
602            version,
603            flags,
604            grouping_type,
605            grouping_type_parameter,
606            entries,
607        })
608    }
609}
610
611impl<'a> Parse<'a> for SampleToGroupBox {
612    type Error = Error;
613    fn parse(bytes: &'a [u8]) -> Result<Self> {
614        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
615            return Err(Error::BufferTooShort {
616                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
617                have: bytes.len(),
618                what: "sbgp box",
619            });
620        }
621        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
622        if ty != SBGP_TYPE {
623            return Err(Error::InvalidValue {
624                field: "box_type",
625                value: ty as u64,
626                reason: "expected sbgp",
627            });
628        }
629        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
630    }
631}
632
633impl Serialize for SampleToGroupBox {
634    type Error = Error;
635    fn serialized_len(&self) -> usize {
636        let gtp_size = if self.version == 1 { 4 } else { 0 };
637        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + gtp_size + 4 + self.entries.len() * 8
638    }
639    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
640        let need = self.serialized_len();
641        if buf.len() < need {
642            return Err(Error::OutputBufferTooSmall {
643                need,
644                have: buf.len(),
645            });
646        }
647        let mut c = 0;
648        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
649        c += 4;
650        buf[c..c + 4].copy_from_slice(b"sbgp");
651        c += 4;
652        buf[c] = self.version;
653        let fb = self.flags.to_be_bytes();
654        buf[c + 1] = fb[1];
655        buf[c + 2] = fb[2];
656        buf[c + 3] = fb[3];
657        c += 4;
658        buf[c..c + 4].copy_from_slice(&self.grouping_type.to_be_bytes());
659        c += 4;
660        if self.version == 1 {
661            let gtp = self.grouping_type_parameter.unwrap_or(0);
662            buf[c..c + 4].copy_from_slice(&gtp.to_be_bytes());
663            c += 4;
664        }
665        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
666        c += 4;
667        for entry in &self.entries {
668            buf[c..c + 4].copy_from_slice(&entry.sample_count.to_be_bytes());
669            buf[c + 4..c + 8].copy_from_slice(&entry.group_description_index.to_be_bytes());
670            c += 8;
671        }
672        Ok(c)
673    }
674}
675
676// ---------------------------------------------------------------------------
677// SubSampleInformationBox — subs (ISO/IEC 14496-12:2015 §8.7.7)
678// ---------------------------------------------------------------------------
679
680/// A single sub-sample descriptor within a [`SubsEntry`].
681///
682/// `subsample_size` width depends on the `subs` box version:
683/// version 0 → u16; version 1 → u32. Stored as u32 in both cases.
684#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685#[cfg_attr(feature = "serde", derive(serde::Serialize))]
686pub struct SubSampleDescriptor {
687    /// Size in bytes (u16 on wire for v0, u32 for v1).
688    pub subsample_size: u32,
689    /// Degradation priority (higher = more important, §8.7.7.3).
690    pub subsample_priority: u8,
691    /// 0 = required; 1 = discardable (§8.7.7.3).
692    pub discardable: u8,
693    /// Codec-specific parameters (§8.7.7.3); 0 if not defined.
694    pub codec_specific_parameters: u32,
695}
696
697impl SubSampleDescriptor {
698    fn wire_len(version: u8) -> usize {
699        let size_field = if version == 1 { 4 } else { 2 };
700        size_field + 1 + 1 + 4
701    }
702}
703
704/// Per-sample entry in the subs table (§8.7.7.2).
705#[derive(Debug, Clone, PartialEq, Eq)]
706#[cfg_attr(feature = "serde", derive(serde::Serialize))]
707pub struct SubsEntry {
708    /// Delta from the previous entry's sample number (or from 0 for the first
709    /// entry), giving the sample number of this entry's first described sample.
710    pub sample_delta: u32,
711    /// Sub-sample descriptors for this sample (may be empty).
712    pub subsamples: Vec<SubSampleDescriptor>,
713}
714
715impl SubsEntry {
716    fn wire_len(&self, version: u8) -> usize {
717        4 + 2 + self.subsamples.len() * SubSampleDescriptor::wire_len(version)
718    }
719}
720
721/// Sub-Sample Information Box (`subs`) — ISO/IEC 14496-12:2015 §8.7.7.2.
722///
723/// Wire layout (FullBox header omitted):
724///
725/// ```text
726/// entry_count     u(32)
727/// for each entry:
728///   sample_delta    u(32)
729///   subsample_count u(16)
730///   for each subsample:
731///     subsample_size      u(16) if version == 0, u(32) if version == 1
732///     subsample_priority  u(8)
733///     discardable         u(8)
734///     codec_specific_parameters u(32)
735/// ```
736#[derive(Debug, Clone, PartialEq, Eq)]
737#[cfg_attr(feature = "serde", derive(serde::Serialize))]
738pub struct SubSampleInformationBox {
739    /// FullBox version: 0 = `subsample_size` is u16; 1 = u32.
740    pub version: u8,
741    /// FullBox flags `[23:0]`.
742    pub flags: u32,
743    /// Sample entries (each covering one sample with sub-sample structure).
744    pub entries: Vec<SubsEntry>,
745}
746
747impl SubSampleInformationBox {
748    /// Parse the body of a `subs` box (after the 8-byte BoxHeader).
749    pub fn parse_body(body: &[u8]) -> Result<Self> {
750        if body.len() < FULLBOX_EXTRA_SIZE + 4 {
751            return Err(Error::BufferTooShort {
752                need: FULLBOX_EXTRA_SIZE + 4,
753                have: body.len(),
754                what: "subs body",
755            });
756        }
757        let version = body[0];
758        let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
759        let mut c = FULLBOX_EXTRA_SIZE;
760        let entry_count =
761            u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]) as usize;
762        c += 4;
763        let ss_size_field = if version == 1 { 4usize } else { 2usize };
764        let mut entries = Vec::with_capacity(entry_count);
765        for _ in 0..entry_count {
766            if body.len() < c + 4 + 2 {
767                return Err(Error::BufferTooShort {
768                    need: c + 6,
769                    have: body.len(),
770                    what: "subs entry header",
771                });
772            }
773            let sample_delta = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
774            c += 4;
775            let subsample_count = u16::from_be_bytes([body[c], body[c + 1]]) as usize;
776            c += 2;
777            let ss_wire = ss_size_field + 1 + 1 + 4;
778            let mut subsamples = Vec::with_capacity(subsample_count);
779            for _ in 0..subsample_count {
780                if body.len() < c + ss_wire {
781                    return Err(Error::BufferTooShort {
782                        need: c + ss_wire,
783                        have: body.len(),
784                        what: "subs subsample",
785                    });
786                }
787                let subsample_size = if version == 1 {
788                    let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
789                    c += 4;
790                    v
791                } else {
792                    let v = u16::from_be_bytes([body[c], body[c + 1]]) as u32;
793                    c += 2;
794                    v
795                };
796                let subsample_priority = body[c];
797                let discardable = body[c + 1];
798                c += 2;
799                let codec_specific_parameters =
800                    u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
801                c += 4;
802                subsamples.push(SubSampleDescriptor {
803                    subsample_size,
804                    subsample_priority,
805                    discardable,
806                    codec_specific_parameters,
807                });
808            }
809            entries.push(SubsEntry {
810                sample_delta,
811                subsamples,
812            });
813        }
814        Ok(Self {
815            version,
816            flags,
817            entries,
818        })
819    }
820}
821
822impl<'a> Parse<'a> for SubSampleInformationBox {
823    type Error = Error;
824    fn parse(bytes: &'a [u8]) -> Result<Self> {
825        if bytes.len() < BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 {
826            return Err(Error::BufferTooShort {
827                need: BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4,
828                have: bytes.len(),
829                what: "subs box",
830            });
831        }
832        let ty = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
833        if ty != SUBS_TYPE {
834            return Err(Error::InvalidValue {
835                field: "box_type",
836                value: ty as u64,
837                reason: "expected subs",
838            });
839        }
840        Self::parse_body(&bytes[BOX_HEADER_SIZE..])
841    }
842}
843
844impl Serialize for SubSampleInformationBox {
845    type Error = Error;
846    fn serialized_len(&self) -> usize {
847        let entries_size: usize = self.entries.iter().map(|e| e.wire_len(self.version)).sum();
848        BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4 + entries_size
849    }
850    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
851        let need = self.serialized_len();
852        if buf.len() < need {
853            return Err(Error::OutputBufferTooSmall {
854                need,
855                have: buf.len(),
856            });
857        }
858        let mut c = 0;
859        buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
860        c += 4;
861        buf[c..c + 4].copy_from_slice(b"subs");
862        c += 4;
863        buf[c] = self.version;
864        let fb = self.flags.to_be_bytes();
865        buf[c + 1] = fb[1];
866        buf[c + 2] = fb[2];
867        buf[c + 3] = fb[3];
868        c += 4;
869        buf[c..c + 4].copy_from_slice(&(self.entries.len() as u32).to_be_bytes());
870        c += 4;
871        for entry in &self.entries {
872            buf[c..c + 4].copy_from_slice(&entry.sample_delta.to_be_bytes());
873            c += 4;
874            buf[c..c + 2].copy_from_slice(&(entry.subsamples.len() as u16).to_be_bytes());
875            c += 2;
876            for ss in &entry.subsamples {
877                if self.version == 1 {
878                    buf[c..c + 4].copy_from_slice(&ss.subsample_size.to_be_bytes());
879                    c += 4;
880                } else {
881                    buf[c..c + 2].copy_from_slice(&(ss.subsample_size as u16).to_be_bytes());
882                    c += 2;
883                }
884                buf[c] = ss.subsample_priority;
885                buf[c + 1] = ss.discardable;
886                c += 2;
887                buf[c..c + 4].copy_from_slice(&ss.codec_specific_parameters.to_be_bytes());
888                c += 4;
889            }
890        }
891        Ok(c)
892    }
893}
894
895// ---------------------------------------------------------------------------
896// Unit tests
897// ---------------------------------------------------------------------------
898
899#[cfg(test)]
900mod tests {
901    use super::*;
902    use broadcast_common::Serialize;
903
904    // -----------------------------------------------------------------------
905    // prft
906    // -----------------------------------------------------------------------
907
908    #[test]
909    fn prft_round_trip_v0() {
910        let b = ProducerReferenceTimeBox {
911            version: 0,
912            flags: 0,
913            reference_track_id: 1,
914            ntp_timestamp: 0x1234_5678_9abc_def0,
915            media_time: 0x0000_0000_0000_1234,
916        };
917        let bytes = b.to_bytes();
918        assert_eq!(bytes.len(), 8 + 4 + 4 + 8 + 4);
919        let parsed = ProducerReferenceTimeBox::parse(&bytes).unwrap();
920        assert_eq!(parsed, b);
921    }
922
923    #[test]
924    fn prft_round_trip_v1() {
925        let b = ProducerReferenceTimeBox {
926            version: 1,
927            flags: 0x000018,
928            reference_track_id: 1,
929            ntp_timestamp: 0xedefe3e3_a7ae147a,
930            media_time: 0x0000_0000_0000_1c20,
931        };
932        let bytes = b.to_bytes();
933        assert_eq!(bytes.len(), 8 + 4 + 4 + 8 + 8);
934        let parsed = ProducerReferenceTimeBox::parse(&bytes).unwrap();
935        assert_eq!(parsed, b);
936    }
937
938    // -----------------------------------------------------------------------
939    // sgpd
940    // -----------------------------------------------------------------------
941
942    #[test]
943    fn sgpd_round_trip_roll_v1() {
944        let b = SampleGroupDescriptionBox {
945            version: 1,
946            flags: 0,
947            grouping_type: GROUPING_TYPE_ROLL,
948            default_length: 2,
949            entries: vec![SgpdEntry::Roll { roll_distance: -1 }],
950        };
951        let bytes = b.to_bytes();
952        let parsed = SampleGroupDescriptionBox::parse(&bytes).unwrap();
953        assert_eq!(parsed.entries.len(), 1);
954        assert_eq!(parsed.entries[0], SgpdEntry::Roll { roll_distance: -1 });
955        assert_eq!(parsed.to_bytes(), bytes);
956    }
957
958    #[test]
959    fn sgpd_round_trip_two_roll_entries() {
960        let b = SampleGroupDescriptionBox {
961            version: 1,
962            flags: 0,
963            grouping_type: GROUPING_TYPE_ROLL,
964            default_length: 2,
965            entries: vec![
966                SgpdEntry::Roll { roll_distance: -4 },
967                SgpdEntry::Roll { roll_distance: -1 },
968            ],
969        };
970        let bytes = b.to_bytes();
971        let parsed = SampleGroupDescriptionBox::parse(&bytes).unwrap();
972        assert_eq!(parsed.entries.len(), 2);
973        assert_eq!(parsed.to_bytes(), bytes);
974    }
975
976    // -----------------------------------------------------------------------
977    // sbgp
978    // -----------------------------------------------------------------------
979
980    #[test]
981    fn sbgp_round_trip_v0() {
982        let b = SampleToGroupBox {
983            version: 0,
984            flags: 0,
985            grouping_type: GROUPING_TYPE_ROLL,
986            grouping_type_parameter: None,
987            entries: vec![
988                SbgpEntry {
989                    sample_count: 1,
990                    group_description_index: 1,
991                },
992                SbgpEntry {
993                    sample_count: 10,
994                    group_description_index: 0,
995                },
996            ],
997        };
998        let bytes = b.to_bytes();
999        let parsed = SampleToGroupBox::parse(&bytes).unwrap();
1000        assert_eq!(parsed, b);
1001    }
1002
1003    #[test]
1004    fn sbgp_round_trip_v1() {
1005        let b = SampleToGroupBox {
1006            version: 1,
1007            flags: 0,
1008            grouping_type: GROUPING_TYPE_ROLL,
1009            grouping_type_parameter: Some(0xDEAD_BEEF),
1010            entries: vec![SbgpEntry {
1011                sample_count: 5,
1012                group_description_index: 1,
1013            }],
1014        };
1015        let bytes = b.to_bytes();
1016        let parsed = SampleToGroupBox::parse(&bytes).unwrap();
1017        assert_eq!(parsed, b);
1018    }
1019
1020    // -----------------------------------------------------------------------
1021    // subs
1022    // -----------------------------------------------------------------------
1023
1024    #[test]
1025    fn subs_round_trip_v0() {
1026        let b = SubSampleInformationBox {
1027            version: 0,
1028            flags: 0,
1029            entries: vec![SubsEntry {
1030                sample_delta: 1,
1031                subsamples: vec![
1032                    SubSampleDescriptor {
1033                        subsample_size: 100,
1034                        subsample_priority: 255,
1035                        discardable: 0,
1036                        codec_specific_parameters: 0,
1037                    },
1038                    SubSampleDescriptor {
1039                        subsample_size: 200,
1040                        subsample_priority: 128,
1041                        discardable: 1,
1042                        codec_specific_parameters: 0,
1043                    },
1044                ],
1045            }],
1046        };
1047        let bytes = b.to_bytes();
1048        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1049        assert_eq!(parsed, b);
1050        assert_eq!(parsed.to_bytes(), bytes);
1051    }
1052
1053    #[test]
1054    fn subs_round_trip_v1() {
1055        let b = SubSampleInformationBox {
1056            version: 1,
1057            flags: 0,
1058            entries: vec![SubsEntry {
1059                sample_delta: 5,
1060                subsamples: vec![SubSampleDescriptor {
1061                    subsample_size: 0x0001_2345,
1062                    subsample_priority: 200,
1063                    discardable: 0,
1064                    codec_specific_parameters: 0xABCD_EF01,
1065                }],
1066            }],
1067        };
1068        let bytes = b.to_bytes();
1069        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1070        assert_eq!(parsed, b);
1071        assert_eq!(parsed.to_bytes(), bytes);
1072    }
1073
1074    #[test]
1075    fn subs_empty_round_trip() {
1076        let b = SubSampleInformationBox {
1077            version: 0,
1078            flags: 0,
1079            entries: vec![],
1080        };
1081        let bytes = b.to_bytes();
1082        let parsed = SubSampleInformationBox::parse(&bytes).unwrap();
1083        assert_eq!(parsed, b);
1084    }
1085}