Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/
sgpd.rs

1use crate::*;
2
3/// SampleGroupDescriptionBox, ISO/IEC 14496-12:2024 Sect 8.9.3
4#[derive(Debug, Clone, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Sgpd {
7    pub grouping_type: FourCC,
8    pub default_length: Option<u32>,
9    pub default_group_description_index: Option<u32>,
10    pub static_group_description: bool,
11    pub static_mapping: bool,
12    pub essential: bool,
13    pub entries: Vec<SgpdEntry>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct SgpdEntry {
19    pub description_length: Option<u32>,
20    pub entry: AnySampleGroupEntry,
21}
22
23ext!(
24    name: Sgpd,
25    versions: [0, 1, 2, 3],
26    flags: {
27        static_group_description = 0,
28        static_mapping = 1,
29    }
30);
31
32impl PartialOrd for SgpdVersion {
33    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
34        match (self, other) {
35            (SgpdVersion::V0, SgpdVersion::V0) => Some(std::cmp::Ordering::Equal),
36            (SgpdVersion::V0, _) => Some(std::cmp::Ordering::Less),
37            (SgpdVersion::V1, SgpdVersion::V0) => Some(std::cmp::Ordering::Greater),
38            (SgpdVersion::V1, SgpdVersion::V1) => Some(std::cmp::Ordering::Equal),
39            (SgpdVersion::V1, _) => Some(std::cmp::Ordering::Less),
40            (SgpdVersion::V2, SgpdVersion::V2) => Some(std::cmp::Ordering::Equal),
41            (SgpdVersion::V2, SgpdVersion::V3) => Some(std::cmp::Ordering::Less),
42            (SgpdVersion::V2, _) => Some(std::cmp::Ordering::Greater),
43            (SgpdVersion::V3, SgpdVersion::V3) => Some(std::cmp::Ordering::Equal),
44            (SgpdVersion::V3, _) => Some(std::cmp::Ordering::Greater),
45        }
46    }
47}
48
49impl AtomExt for Sgpd {
50    type Ext = SgpdExt;
51
52    const KIND_EXT: FourCC = FourCC::new(b"sgpd");
53
54    fn decode_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self> {
55        let grouping_type = FourCC::decode(buf)?;
56        let default_length = if ext.version >= SgpdVersion::V1 {
57            Some(u32::decode(buf)?)
58        } else {
59            None
60        };
61        let default_group_description_index = if ext.version >= SgpdVersion::V2 {
62            Some(u32::decode(buf)?)
63        } else {
64            None
65        };
66        let entry_count = u32::decode(buf)?;
67        // Every entry takes at least a byte, so reject counts that cannot fit before
68        // looping: a version 0 entry of an unknown grouping type reads to the end of the
69        // box, leaving every later entry empty and free to decode, so a 20-byte box could
70        // otherwise push billions of them.
71        if entry_count as u64 > buf.remaining() as u64 {
72            return Err(Error::OutOfBounds);
73        }
74        let mut entries = Vec::with_capacity((entry_count as usize).min(1024));
75        for _ in 0..entry_count {
76            // Spec states: if version>=1 && default_length==0
77            // But, default_length.is_some(), if and only if version>=1, so fine to just check for
78            // `Some(0)`.
79            let description_length = if default_length == Some(0) {
80                Some(u32::decode(buf)?)
81            } else {
82                default_length
83            };
84            let entry = AnySampleGroupEntry::decode(grouping_type, description_length, buf)?;
85            entries.push(SgpdEntry {
86                description_length,
87                entry,
88            });
89        }
90        let static_group_description = ext.static_group_description;
91        let static_mapping = ext.static_mapping;
92        let essential = ext.version == SgpdVersion::V3;
93        Ok(Self {
94            grouping_type,
95            default_length,
96            default_group_description_index,
97            static_group_description,
98            static_mapping,
99            essential,
100            entries,
101        })
102    }
103
104    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext> {
105        let version = if self.essential {
106            SgpdVersion::V3
107        } else if self.default_group_description_index.is_some() {
108            SgpdVersion::V2
109        } else if self.default_length.is_some() {
110            SgpdVersion::V1
111        } else {
112            SgpdVersion::V0
113        };
114        let ext = SgpdExt {
115            version,
116            static_group_description: self.static_group_description,
117            static_mapping: self.static_mapping,
118        };
119        self.grouping_type.encode(buf)?;
120        if version >= SgpdVersion::V1 {
121            self.default_length
122                .ok_or(Error::MissingContent("sgpd default_length"))?
123                .encode(buf)?;
124        }
125        if version >= SgpdVersion::V2 {
126            self.default_group_description_index
127                .ok_or(Error::MissingContent(
128                    "sgpd default_group_description_index",
129                ))?
130                .encode(buf)?;
131        }
132        (self.entries.len() as u32).encode(buf)?;
133        for entry in &self.entries {
134            if self.default_length == Some(0) {
135                if let Some(description_length) = entry.description_length {
136                    description_length.encode(buf)?
137                }
138            }
139            entry.entry.encode(buf)?
140        }
141        Ok(ext)
142    }
143}
144
145const REFS_4CC: FourCC = FourCC::new(b"refs");
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149pub enum AnySampleGroupEntry {
150    DirectReferenceSampleList(u32, Vec<u32>),
151    UnknownGroupingType(FourCC, Vec<u8>),
152}
153
154impl AnySampleGroupEntry {
155    fn decode<B: Buf>(
156        grouping_type: FourCC,
157        description_length: Option<u32>,
158        buf: &mut B,
159    ) -> Result<Self> {
160        match grouping_type {
161            REFS_4CC => {
162                let sample_id = u32::decode(buf)?;
163                let num_direct_reference_samples = u8::decode(buf)? as usize;
164                let mut direct_reference_samples =
165                    Vec::with_capacity(std::cmp::min(num_direct_reference_samples, 16));
166                for _ in 0..num_direct_reference_samples {
167                    direct_reference_samples.push(u32::decode(buf)?);
168                }
169                Ok(Self::DirectReferenceSampleList(
170                    sample_id,
171                    direct_reference_samples,
172                ))
173            }
174            _ => {
175                let bytes = match description_length {
176                    Some(description_length) => {
177                        Vec::decode_exact(buf, description_length as usize)?
178                    }
179                    None => Vec::decode(buf)?,
180                };
181                Ok(Self::UnknownGroupingType(grouping_type, bytes))
182            }
183        }
184    }
185
186    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
187        match self {
188            Self::DirectReferenceSampleList(sample_id, direct_reference_samples) => {
189                sample_id.encode(buf)?;
190                let num_direct_reference_samples: u8 = direct_reference_samples
191                    .len()
192                    .try_into()
193                    .map_err(|_| Error::TooLarge(REFS_4CC))?;
194                num_direct_reference_samples.encode(buf)?;
195                for direct_reference_sample in direct_reference_samples {
196                    direct_reference_sample.encode(buf)?;
197                }
198                Ok(())
199            }
200            Self::UnknownGroupingType(_, bytes) => bytes.encode(buf),
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use std::io::Cursor;
209
210    // This example was taken from:
211    // https://mpeggroup.github.io/FileFormatConformance/files/published/isobmff/a9-aac-samplegroups-edit.mp4
212    //
213    // I just extracted the bytes for the sgpd atom location.
214    const SIMPLE_SGPD: &[u8] = &[
215        0x00, 0x00, 0x00, 0x1A, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x6F, 0x6C,
216        0x6C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
217    ];
218
219    #[test]
220    fn sgpd_decodes_from_bytes_correctly() {
221        let mut buf = Cursor::new(SIMPLE_SGPD);
222        let sgpd = Sgpd::decode(&mut buf).expect("sgpd should decode successfully");
223        assert_eq!(
224            sgpd,
225            Sgpd {
226                grouping_type: FourCC::from(b"roll"),
227                default_length: Some(2),
228                default_group_description_index: None,
229                static_group_description: false,
230                static_mapping: false,
231                essential: false,
232                entries: vec![SgpdEntry {
233                    description_length: Some(2),
234                    entry: AnySampleGroupEntry::UnknownGroupingType(
235                        FourCC::from(b"roll"),
236                        SIMPLE_SGPD[24..].to_vec()
237                    )
238                }],
239            }
240        )
241    }
242
243    #[test]
244    fn sgpd_encodes_from_type_correctly() {
245        let sgpd = Sgpd {
246            grouping_type: FourCC::from(b"roll"),
247            default_length: Some(2),
248            default_group_description_index: None,
249            static_group_description: false,
250            static_mapping: false,
251            essential: false,
252            entries: vec![SgpdEntry {
253                description_length: Some(2),
254                entry: AnySampleGroupEntry::UnknownGroupingType(
255                    FourCC::from(b"roll"),
256                    SIMPLE_SGPD[24..].to_vec(),
257                ),
258            }],
259        };
260        let mut buf = Vec::new();
261        sgpd.encode(&mut buf).expect("encode should be successful");
262        assert_eq!(SIMPLE_SGPD, &buf);
263    }
264
265    #[test]
266    fn sgpd_unknown_entries_respect_description_length() {
267        const MULTIPLE_ENTRIES: &[u8] = &[
268            0x00, 0x00, 0x00, 0x1C, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65,
269            0x73, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD,
270        ];
271
272        let sgpd = Sgpd::decode(&mut Cursor::new(MULTIPLE_ENTRIES))
273            .expect("sgpd should decode successfully");
274
275        assert_eq!(
276            sgpd.entries,
277            vec![
278                SgpdEntry {
279                    description_length: Some(2),
280                    entry: AnySampleGroupEntry::UnknownGroupingType(
281                        FourCC::from(b"test"),
282                        vec![0xAA, 0xBB],
283                    ),
284                },
285                SgpdEntry {
286                    description_length: Some(2),
287                    entry: AnySampleGroupEntry::UnknownGroupingType(
288                        FourCC::from(b"test"),
289                        vec![0xCC, 0xDD],
290                    ),
291                },
292            ]
293        );
294    }
295
296    #[test]
297    fn sgpd_rejects_entry_count_past_the_box() {
298        // Version 0 of an unknown grouping type, declaring 0xffff0000 entries in a
299        // 20-byte box: the first reads nothing, and every later one would be empty.
300        const HUGE_COUNT: &[u8] = &[
301            0x00, 0x00, 0x00, 0x14, 0x73, 0x67, 0x70, 0x64, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
302            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
303        ];
304
305        let err = Sgpd::decode(&mut Cursor::new(HUGE_COUNT)).unwrap_err();
306        assert!(matches!(err, Error::OverDecode(_)), "{err:?}");
307    }
308
309    #[test]
310    fn sgpd_version_0_unknown_entries_still_decode() {
311        // Two version 0 entries of an unknown type: the first reads the rest of the
312        // box, as before, and the count still fits the bytes left.
313        const VERSION_0: &[u8] = &[
314            0x00, 0x00, 0x00, 0x18, 0x73, 0x67, 0x70, 0x64, 0x00, 0x00, 0x00, 0x00, 0x72, 0x6F,
315            0x6C, 0x6C, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFE,
316        ];
317
318        let sgpd = Sgpd::decode(&mut Cursor::new(VERSION_0)).expect("sgpd should decode");
319        assert_eq!(sgpd.entries.len(), 2);
320        assert_eq!(
321            sgpd.entries[0].entry,
322            AnySampleGroupEntry::UnknownGroupingType(
323                FourCC::from(b"roll"),
324                vec![0xFF, 0xFF, 0xFF, 0xFE]
325            )
326        );
327    }
328
329    #[test]
330    fn sgpd_essential_requires_version_3_defaults() {
331        let mut sgpd = Sgpd {
332            grouping_type: FourCC::from(b"test"),
333            default_length: None,
334            default_group_description_index: None,
335            static_group_description: false,
336            static_mapping: false,
337            essential: true,
338            entries: vec![],
339        };
340
341        let err = sgpd.encode(&mut Vec::new()).unwrap_err();
342        assert!(matches!(err, Error::MissingContent("sgpd default_length")));
343
344        sgpd.default_length = Some(0);
345        let err = sgpd.encode(&mut Vec::new()).unwrap_err();
346        assert!(matches!(
347            err,
348            Error::MissingContent("sgpd default_group_description_index")
349        ));
350    }
351
352    // From the MPEG File Format Conformance suite, heif/C041.heic
353    const SGPD_ENCODED_C041: &[u8] = &[
354        0x00, 0x00, 0x00, 0x2e, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x65, 0x66,
355        0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00,
356        0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
357        0x00,
358    ];
359
360    #[test]
361    fn sgpd_c041_decode() {
362        let mut buf = Cursor::new(SGPD_ENCODED_C041);
363        let sgpd = Sgpd::decode(&mut buf).expect("sgpd should decode successfully");
364        assert_eq!(
365            sgpd,
366            Sgpd {
367                grouping_type: FourCC::from(b"refs"),
368                default_length: Some(0),
369                default_group_description_index: None,
370                static_group_description: false,
371                static_mapping: false,
372                essential: false,
373                entries: vec![
374                    SgpdEntry {
375                        description_length: Some(9),
376                        entry: AnySampleGroupEntry::DirectReferenceSampleList(0, vec![1])
377                    },
378                    SgpdEntry {
379                        description_length: Some(5),
380                        entry: AnySampleGroupEntry::DirectReferenceSampleList(1, vec![])
381                    }
382                ],
383            }
384        );
385
386        let mut encoded = Vec::new();
387        sgpd.encode(&mut encoded).unwrap();
388
389        assert_eq!(encoded, SGPD_ENCODED_C041);
390    }
391}