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        let mut entries = Vec::with_capacity((entry_count as usize).min(1024));
68        for _ in 0..entry_count {
69            // Spec states: if version>=1 && default_length==0
70            // But, default_length.is_some(), if and only if version>=1, so fine to just check for
71            // `Some(0)`.
72            let description_length = if default_length == Some(0) {
73                Some(u32::decode(buf)?)
74            } else {
75                default_length
76            };
77            let entry = AnySampleGroupEntry::decode(grouping_type, description_length, buf)?;
78            entries.push(SgpdEntry {
79                description_length,
80                entry,
81            });
82        }
83        let static_group_description = ext.static_group_description;
84        let static_mapping = ext.static_mapping;
85        let essential = ext.version == SgpdVersion::V3;
86        Ok(Self {
87            grouping_type,
88            default_length,
89            default_group_description_index,
90            static_group_description,
91            static_mapping,
92            essential,
93            entries,
94        })
95    }
96
97    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext> {
98        let version = if self.essential {
99            SgpdVersion::V3
100        } else if self.default_group_description_index.is_some() {
101            SgpdVersion::V2
102        } else if self.default_length.is_some() {
103            SgpdVersion::V1
104        } else {
105            SgpdVersion::V0
106        };
107        let ext = SgpdExt {
108            version,
109            static_group_description: self.static_group_description,
110            static_mapping: self.static_mapping,
111        };
112        self.grouping_type.encode(buf)?;
113        if version >= SgpdVersion::V1 {
114            self.default_length
115                .ok_or(Error::MissingContent("sgpd default_length"))?
116                .encode(buf)?;
117        }
118        if version >= SgpdVersion::V2 {
119            self.default_group_description_index
120                .ok_or(Error::MissingContent(
121                    "sgpd default_group_description_index",
122                ))?
123                .encode(buf)?;
124        }
125        (self.entries.len() as u32).encode(buf)?;
126        for entry in &self.entries {
127            if self.default_length == Some(0) {
128                if let Some(description_length) = entry.description_length {
129                    description_length.encode(buf)?
130                }
131            }
132            entry.entry.encode(buf)?
133        }
134        Ok(ext)
135    }
136}
137
138const REFS_4CC: FourCC = FourCC::new(b"refs");
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub enum AnySampleGroupEntry {
143    DirectReferenceSampleList(u32, Vec<u32>),
144    UnknownGroupingType(FourCC, Vec<u8>),
145}
146
147impl AnySampleGroupEntry {
148    fn decode<B: Buf>(
149        grouping_type: FourCC,
150        description_length: Option<u32>,
151        buf: &mut B,
152    ) -> Result<Self> {
153        match grouping_type {
154            REFS_4CC => {
155                let sample_id = u32::decode(buf)?;
156                let num_direct_reference_samples = u8::decode(buf)? as usize;
157                let mut direct_reference_samples =
158                    Vec::with_capacity(std::cmp::min(num_direct_reference_samples, 16));
159                for _ in 0..num_direct_reference_samples {
160                    direct_reference_samples.push(u32::decode(buf)?);
161                }
162                Ok(Self::DirectReferenceSampleList(
163                    sample_id,
164                    direct_reference_samples,
165                ))
166            }
167            _ => {
168                let bytes = match description_length {
169                    Some(description_length) => {
170                        Vec::decode_exact(buf, description_length as usize)?
171                    }
172                    None => Vec::decode(buf)?,
173                };
174                Ok(Self::UnknownGroupingType(grouping_type, bytes))
175            }
176        }
177    }
178
179    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
180        match self {
181            Self::DirectReferenceSampleList(sample_id, direct_reference_samples) => {
182                sample_id.encode(buf)?;
183                let num_direct_reference_samples: u8 = direct_reference_samples
184                    .len()
185                    .try_into()
186                    .map_err(|_| Error::TooLarge(REFS_4CC))?;
187                num_direct_reference_samples.encode(buf)?;
188                for direct_reference_sample in direct_reference_samples {
189                    direct_reference_sample.encode(buf)?;
190                }
191                Ok(())
192            }
193            Self::UnknownGroupingType(_, bytes) => bytes.encode(buf),
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use std::io::Cursor;
202
203    // This example was taken from:
204    // https://mpeggroup.github.io/FileFormatConformance/files/published/isobmff/a9-aac-samplegroups-edit.mp4
205    //
206    // I just extracted the bytes for the sgpd atom location.
207    const SIMPLE_SGPD: &[u8] = &[
208        0x00, 0x00, 0x00, 0x1A, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x6F, 0x6C,
209        0x6C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
210    ];
211
212    #[test]
213    fn sgpd_decodes_from_bytes_correctly() {
214        let mut buf = Cursor::new(SIMPLE_SGPD);
215        let sgpd = Sgpd::decode(&mut buf).expect("sgpd should decode successfully");
216        assert_eq!(
217            sgpd,
218            Sgpd {
219                grouping_type: FourCC::from(b"roll"),
220                default_length: Some(2),
221                default_group_description_index: None,
222                static_group_description: false,
223                static_mapping: false,
224                essential: false,
225                entries: vec![SgpdEntry {
226                    description_length: Some(2),
227                    entry: AnySampleGroupEntry::UnknownGroupingType(
228                        FourCC::from(b"roll"),
229                        SIMPLE_SGPD[24..].to_vec()
230                    )
231                }],
232            }
233        )
234    }
235
236    #[test]
237    fn sgpd_encodes_from_type_correctly() {
238        let sgpd = Sgpd {
239            grouping_type: FourCC::from(b"roll"),
240            default_length: Some(2),
241            default_group_description_index: None,
242            static_group_description: false,
243            static_mapping: false,
244            essential: false,
245            entries: vec![SgpdEntry {
246                description_length: Some(2),
247                entry: AnySampleGroupEntry::UnknownGroupingType(
248                    FourCC::from(b"roll"),
249                    SIMPLE_SGPD[24..].to_vec(),
250                ),
251            }],
252        };
253        let mut buf = Vec::new();
254        sgpd.encode(&mut buf).expect("encode should be successful");
255        assert_eq!(SIMPLE_SGPD, &buf);
256    }
257
258    #[test]
259    fn sgpd_unknown_entries_respect_description_length() {
260        const MULTIPLE_ENTRIES: &[u8] = &[
261            0x00, 0x00, 0x00, 0x1C, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65,
262            0x73, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD,
263        ];
264
265        let sgpd = Sgpd::decode(&mut Cursor::new(MULTIPLE_ENTRIES))
266            .expect("sgpd should decode successfully");
267
268        assert_eq!(
269            sgpd.entries,
270            vec![
271                SgpdEntry {
272                    description_length: Some(2),
273                    entry: AnySampleGroupEntry::UnknownGroupingType(
274                        FourCC::from(b"test"),
275                        vec![0xAA, 0xBB],
276                    ),
277                },
278                SgpdEntry {
279                    description_length: Some(2),
280                    entry: AnySampleGroupEntry::UnknownGroupingType(
281                        FourCC::from(b"test"),
282                        vec![0xCC, 0xDD],
283                    ),
284                },
285            ]
286        );
287    }
288
289    #[test]
290    fn sgpd_essential_requires_version_3_defaults() {
291        let mut sgpd = Sgpd {
292            grouping_type: FourCC::from(b"test"),
293            default_length: None,
294            default_group_description_index: None,
295            static_group_description: false,
296            static_mapping: false,
297            essential: true,
298            entries: vec![],
299        };
300
301        let err = sgpd.encode(&mut Vec::new()).unwrap_err();
302        assert!(matches!(err, Error::MissingContent("sgpd default_length")));
303
304        sgpd.default_length = Some(0);
305        let err = sgpd.encode(&mut Vec::new()).unwrap_err();
306        assert!(matches!(
307            err,
308            Error::MissingContent("sgpd default_group_description_index")
309        ));
310    }
311
312    // From the MPEG File Format Conformance suite, heif/C041.heic
313    const SGPD_ENCODED_C041: &[u8] = &[
314        0x00, 0x00, 0x00, 0x2e, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x65, 0x66,
315        0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00,
316        0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
317        0x00,
318    ];
319
320    #[test]
321    fn sgpd_c041_decode() {
322        let mut buf = Cursor::new(SGPD_ENCODED_C041);
323        let sgpd = Sgpd::decode(&mut buf).expect("sgpd should decode successfully");
324        assert_eq!(
325            sgpd,
326            Sgpd {
327                grouping_type: FourCC::from(b"refs"),
328                default_length: Some(0),
329                default_group_description_index: None,
330                static_group_description: false,
331                static_mapping: false,
332                essential: false,
333                entries: vec![
334                    SgpdEntry {
335                        description_length: Some(9),
336                        entry: AnySampleGroupEntry::DirectReferenceSampleList(0, vec![1])
337                    },
338                    SgpdEntry {
339                        description_length: Some(5),
340                        entry: AnySampleGroupEntry::DirectReferenceSampleList(1, vec![])
341                    }
342                ],
343            }
344        );
345
346        let mut encoded = Vec::new();
347        sgpd.encode(&mut encoded).unwrap();
348
349        assert_eq!(encoded, SGPD_ENCODED_C041);
350    }
351}