Skip to main content

mp4_atom/
sidx.rs

1use crate::*;
2
3// SegmentIndexBox, ISO/IEC 14496-12:2026 Section 8.14.3
4// This is called out in CMAF (23000-19) and DASH (23009-1).
5
6ext! {
7    name: Sidx,
8    versions: [0, 1],
9    flags: {}
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct SegmentReference {
15    pub reference_type: bool,
16    pub referenced_size: u32,
17    pub subsegment_duration: u32,
18    pub starts_with_sap: bool,
19    pub sap_type: u8,
20    pub sap_delta_time: u32,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Default)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Sidx {
26    pub reference_id: u32,
27    pub timescale: u32,
28    pub earliest_presentation_time: u64,
29    pub first_offset: u64,
30    pub references: Vec<SegmentReference>,
31}
32
33impl AtomExt for Sidx {
34    type Ext = SidxExt;
35
36    const KIND_EXT: FourCC = FourCC::new(b"sidx");
37
38    fn decode_body_ext<B: Buf>(buf: &mut B, ext: SidxExt) -> Result<Self> {
39        let reference_id = u32::decode(buf)?;
40        let timescale = u32::decode(buf)?;
41        let (earliest_presentation_time, first_offset) = if ext.version == SidxVersion::V0 {
42            (u32::decode(buf)?.into(), u32::decode(buf)?.into())
43        } else {
44            (u64::decode(buf)?, u64::decode(buf)?)
45        };
46        let _reserved = u16::decode(buf)?;
47        let reference_count = u16::decode(buf)?;
48        let mut references = Vec::with_capacity(std::cmp::min(reference_count as usize, 128));
49        for _ in 0..reference_count {
50            let referenced_type_and_size = u32::decode(buf)?;
51            let reference_type = (referenced_type_and_size & 0x8000_0000) == 0x8000_0000;
52            let referenced_size = referenced_type_and_size & 0x7FFF_FFFF;
53            let subsegment_duration = u32::decode(buf)?;
54            let sap_flag_and_type_and_delta_time = u32::decode(buf)?;
55            let starts_with_sap = (sap_flag_and_type_and_delta_time & 0x8000_0000) == 0x8000_0000;
56            let sap_type = ((sap_flag_and_type_and_delta_time >> 28) & 0b111) as u8;
57            let sap_delta_time = sap_flag_and_type_and_delta_time & 0x0FFF_FFFF;
58            let reference = SegmentReference {
59                reference_type,
60                referenced_size,
61                subsegment_duration,
62                starts_with_sap,
63                sap_type,
64                sap_delta_time,
65            };
66            references.push(reference);
67        }
68
69        Ok(Sidx {
70            reference_id,
71            timescale,
72            earliest_presentation_time,
73            first_offset,
74            references,
75        })
76    }
77
78    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<SidxExt> {
79        self.reference_id.encode(buf)?;
80        self.timescale.encode(buf)?;
81        let version = match (
82            u32::try_from(self.earliest_presentation_time),
83            u32::try_from(self.first_offset),
84        ) {
85            (Ok(earliest_presentation_time), Ok(first_offset)) => {
86                earliest_presentation_time.encode(buf)?;
87                first_offset.encode(buf)?;
88                SidxVersion::V0
89            }
90            _ => {
91                self.earliest_presentation_time.encode(buf)?;
92                self.first_offset.encode(buf)?;
93                SidxVersion::V1
94            }
95        };
96        0u16.encode(buf)?; // reserved
97        let reference_count: u16 = self
98            .references
99            .len()
100            .try_into()
101            .map_err(|_| Error::TooLarge(Self::KIND))?;
102        reference_count.encode(buf)?;
103        for reference in &self.references {
104            if reference.referenced_size > 0x7FFF_FFFF
105                || reference.sap_type > 0b111
106                || reference.sap_delta_time > 0x0FFF_FFFF
107            {
108                return Err(Error::TooLarge(Self::KIND));
109            }
110
111            let referenced_type_and_size: u32 = match reference.reference_type {
112                true => 0x8000_0000 | reference.referenced_size,
113                false => reference.referenced_size,
114            };
115            referenced_type_and_size.encode(buf)?;
116            reference.subsegment_duration.encode(buf)?;
117            let sap_flag_and_type_and_delta_time = match reference.starts_with_sap {
118                true => {
119                    0x8000_0000 | ((reference.sap_type as u32) << 28) | reference.sap_delta_time
120                }
121                false => ((reference.sap_type as u32) << 28) | reference.sap_delta_time,
122            };
123            sap_flag_and_type_and_delta_time.encode(buf)?;
124        }
125        Ok(SidxExt { version })
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    // From MPEG File Format Conformance suite: 21_segment.mp4
134    const ENCODED_SIDX: &[u8] = &[
135        0x00, 0x00, 0x00, 0x2c, 0x73, 0x69, 0x64, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
136        0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
137        0x00, 0x01, 0x00, 0x04, 0xfc, 0x80, 0x00, 0x00, 0x13, 0x80, 0x90, 0x00, 0x00, 0x00,
138    ];
139
140    // Decoded values per 21_segment_gpac.json
141    /*
142    "SegmentIndexBox": {
143      "@Size": "44",
144      "@Type": "sidx",
145      "@Version": "0",
146      "@Flags": "0",
147      "@Specification": "p12",
148      "@Container": "file",
149      "@reference_ID": "1",
150      "@timescale": "100",
151      "@earliest_presentation_time": "0",
152      "@first_offset": "0",
153      "Reference": {
154        "@type": "0",
155        "@size": "326784",
156        "@duration": "4992",
157        "@startsWithSAP": "1",
158        "@SAP_type": "1",
159        "@SAPDeltaTime": "0"
160      }
161      */
162
163    #[test]
164    fn test_sidx_v0_decode() {
165        let buf: &mut std::io::Cursor<&&[u8]> = &mut std::io::Cursor::new(&ENCODED_SIDX);
166        let sidx = Sidx::decode(buf).expect("failed to decode sidx");
167        assert_eq!(
168            sidx,
169            Sidx {
170                reference_id: 1,
171                timescale: 100,
172                earliest_presentation_time: 0,
173                first_offset: 0,
174                references: vec![SegmentReference {
175                    reference_type: false,
176                    referenced_size: 326784,
177                    subsegment_duration: 4992,
178                    starts_with_sap: true,
179                    sap_type: 1,
180                    sap_delta_time: 0,
181                }],
182            }
183        );
184    }
185
186    #[test]
187    fn test_sidx_v0_encode() {
188        let mut buf = Vec::new();
189        let sidx = Sidx {
190            reference_id: 1,
191            timescale: 100,
192            earliest_presentation_time: 0,
193            first_offset: 0,
194            references: vec![SegmentReference {
195                reference_type: false,
196                referenced_size: 326784,
197                subsegment_duration: 4992,
198                starts_with_sap: true,
199                sap_type: 1,
200                sap_delta_time: 0,
201            }],
202        };
203        sidx.encode(&mut buf).unwrap();
204
205        assert_eq!(buf.as_slice(), ENCODED_SIDX);
206    }
207
208    #[test]
209    fn test_sidx_encode_rejects_oversized_reference_fields() {
210        fn assert_too_large(reference: SegmentReference) {
211            let sidx = Sidx {
212                references: vec![reference],
213                ..Default::default()
214            };
215            let mut buf = Vec::new();
216
217            assert!(matches!(
218                sidx.encode(&mut buf),
219                Err(Error::TooLarge(kind)) if kind == Sidx::KIND
220            ));
221        }
222
223        let reference = SegmentReference {
224            reference_type: false,
225            referenced_size: 0,
226            subsegment_duration: 0,
227            starts_with_sap: false,
228            sap_type: 0,
229            sap_delta_time: 0,
230        };
231
232        assert_too_large(SegmentReference {
233            referenced_size: 0x8000_0000,
234            ..reference.clone()
235        });
236        assert_too_large(SegmentReference {
237            sap_type: 0b1000,
238            ..reference.clone()
239        });
240        assert_too_large(SegmentReference {
241            sap_delta_time: 0x1000_0000,
242            ..reference
243        });
244    }
245}