Skip to main content

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

1use crate::*;
2
3/// SubSampleInformationBox, ISO/IEC 14496-12:2024 Sect 8.7.7
4#[derive(Debug, Clone, PartialEq, Eq, Default)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Subs {
7    pub flags: [u8; 3], // flags are codec specific and not defined directly on subs
8    pub entries: Vec<SubsEntry>,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, Default)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct SubsEntry {
14    pub sample_delta: u32,
15    pub subsamples: Vec<SubsSubsample>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct SubsSubsample {
21    pub size: SubsSubsampleSize,
22    pub priority: u8,
23    pub discardable: bool,
24    pub codec_specific_parameters: Vec<u8>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum SubsSubsampleSize {
30    U16(u16),
31    U32(u32),
32}
33impl Default for SubsSubsampleSize {
34    fn default() -> Self {
35        // The precedent set by the `ext!` macro is to set V0 as default. Given that for V0 the
36        // subsample_size is u16, I set 0u16 as the default.
37        Self::U16(0)
38    }
39}
40impl SubsSubsampleSize {
41    pub fn value(&self) -> u32 {
42        match self {
43            Self::U16(n) => u32::from(*n),
44            Self::U32(n) => *n,
45        }
46    }
47}
48
49// We can't use the `ext!` macro to implement `Ext` because we need to keep track of all possible
50// flags. This is because the box doesn't specify any flags directly, and instead:
51// > The semantics of `flags`, if any, shall be supplied for a given coding system. If flags have no
52// > semantics for a given coding system, the flags shall be 0.
53//
54// Therefore, I need to keep all possible flags on the struct, as they may have semantic meaning
55// that we can't know solely based on the definition of subs, but the user may require knowledge of
56// those flags.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub(crate) enum SubsVersion {
59    #[default]
60    V0 = 0,
61    V1 = 1,
62}
63
64impl TryFrom<u8> for SubsVersion {
65    type Error = Error;
66
67    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
68        match value {
69            0 => Ok(Self::V0),
70            1 => Ok(Self::V1),
71            _ => Err(Error::UnknownVersion(value)),
72        }
73    }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Default)]
77pub(crate) struct SubsExt {
78    pub version: SubsVersion,
79    pub flags: [u8; 3],
80}
81
82impl Ext for SubsExt {
83    fn encode(&self) -> Result<u32> {
84        Ok((self.version as u32) << 24
85            | (self.flags[0] as u32) << 16
86            | (self.flags[1] as u32) << 8
87            | (self.flags[2] as u32))
88    }
89
90    fn decode(v: u32) -> Result<Self> {
91        let bytes = v.to_be_bytes();
92        let version = SubsVersion::try_from(bytes[0])?;
93        let flags = [bytes[1], bytes[2], bytes[3]];
94        Ok(Self { version, flags })
95    }
96}
97
98impl AtomExt for Subs {
99    type Ext = SubsExt;
100
101    const KIND_EXT: FourCC = FourCC::new(b"subs");
102
103    fn decode_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self> {
104        let flags = ext.flags;
105        let entry_count = u32::decode(buf)?;
106        let mut entries = Vec::with_capacity((entry_count as usize).min(1024));
107        for _ in 0..entry_count {
108            let sample_delta = u32::decode(buf)?;
109            let subsample_count = u16::decode(buf)?;
110            let mut subsamples = Vec::with_capacity(usize::from(subsample_count).min(1024));
111            for _ in 0..subsample_count {
112                let size = if ext.version == SubsVersion::V1 {
113                    SubsSubsampleSize::U32(u32::decode(buf)?)
114                } else {
115                    SubsSubsampleSize::U16(u16::decode(buf)?)
116                };
117                let priority = u8::decode(buf)?;
118                let discardable = u8::decode(buf)? == 1;
119                let codec_specific_parameters = <[u8; 4]>::decode(buf)?.to_vec();
120                subsamples.push(SubsSubsample {
121                    size,
122                    priority,
123                    discardable,
124                    codec_specific_parameters,
125                });
126            }
127            entries.push(SubsEntry {
128                sample_delta,
129                subsamples,
130            });
131        }
132        Ok(Self { flags, entries })
133    }
134
135    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext> {
136        let ext = match &self
137            .entries
138            .first()
139            .and_then(|e| e.subsamples.first())
140            .map(|s| &s.size)
141        {
142            Some(SubsSubsampleSize::U16(_)) => SubsExt {
143                version: SubsVersion::V0,
144                flags: self.flags,
145            },
146            Some(SubsSubsampleSize::U32(_)) => SubsExt {
147                version: SubsVersion::V1,
148                flags: self.flags,
149            },
150            // Should I store the version somewhere so that I can always decode and encode back to
151            // the exact same bytes?
152            None => SubsExt {
153                version: SubsVersion::default(),
154                flags: self.flags,
155            },
156        };
157        (self.entries.len() as u32).encode(buf)?;
158        for entry in &self.entries {
159            entry.sample_delta.encode(buf)?;
160            (entry.subsamples.len() as u16).encode(buf)?;
161            for subsample in &entry.subsamples {
162                match subsample.size {
163                    SubsSubsampleSize::U16(n) => n.encode(buf)?,
164                    SubsSubsampleSize::U32(n) => n.encode(buf)?,
165                }
166                subsample.priority.encode(buf)?;
167                if subsample.discardable {
168                    1u8.encode(buf)?;
169                } else {
170                    0u8.encode(buf)?;
171                }
172                subsample.codec_specific_parameters.encode(buf)?;
173            }
174        }
175        Ok(ext)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use std::io::Cursor;
183
184    // This example was taken from:
185    // https://mpeggroup.github.io/FileFormatConformance/files/published/uvvu/Solekai007_1920_29_1x1_v7clear.uvu
186    //
187    // I just extracted the bytes for the subs atom location.
188    const SUBS: &[u8] = &[
189        0x00, 0x00, 0x00, 0x16, 0x73, 0x75, 0x62, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
190        0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
191    ];
192
193    #[test]
194    fn subs_decodes_from_bytes_correctly() {
195        let mut buf = Cursor::new(SUBS);
196        let subs = Subs::decode(&mut buf).expect("subs should decode successfully");
197        assert_eq!(
198            subs,
199            Subs {
200                flags: [0, 0, 0],
201                entries: vec![SubsEntry {
202                    sample_delta: 1,
203                    subsamples: vec![],
204                }],
205            }
206        )
207    }
208
209    #[test]
210    fn subs_truncated_codec_parameters_return_error() {
211        let body: &[u8] = &[
212            0x00, 0x00, 0x00, 0x01, // entry_count
213            0x00, 0x00, 0x00, 0x01, // sample_delta
214            0x00, 0x01, // subsample_count
215            0x00, 0x01, // subsample_size
216            0x00, // priority
217            0x00, // discardable
218            0x00, 0x00, 0x00, // truncated codec_specific_parameters
219        ];
220
221        assert!(matches!(
222            Subs::decode_body_ext(
223                &mut Cursor::new(body),
224                SubsExt {
225                    version: SubsVersion::V0,
226                    flags: [0; 3],
227                },
228            ),
229            Err(Error::OutOfBounds)
230        ));
231    }
232
233    // This example was taken from:
234    // https://mpeggroup.github.io/FileFormatConformance/files/published/nalu/hevc/subs_tile_hvc1.mp4
235    //
236    // I just extracted the bytes for the subs atom location and then modified it to make it
237    // shorter.
238    //
239    // I added this example so that I could test subsample decoding/encoding and therefore also have
240    // an encoding test.
241    const SUBS_COMPLEX: &[u8] = &[
242        0x00, 0x00, 0x00, 0x9C, 0x73, 0x75, 0x62, 0x73, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
243        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0F, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
244        0x05, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00,
245        0x00, 0x0B, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xA3, 0x00, 0x00, 0x00, 0x00,
246        0x00, 0x00, 0x05, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4A, 0x00, 0x00, 0x00,
247        0x00, 0x00, 0x00, 0x05, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
248        0x00, 0x08, 0x00, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00,
249        0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xDE, 0x00, 0x00,
250        0x00, 0x00, 0x00, 0x00, 0x05, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x4A, 0x00,
251        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF6,
252        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
253    ];
254
255    #[test]
256    fn subs_decodes_from_bytes_and_encodes_to_bytes_correctly_with_more_complex_example() {
257        let mut buf = Cursor::new(SUBS_COMPLEX);
258        let subs = Subs {
259            flags: [0, 0, 2],
260            entries: vec![
261                SubsEntry {
262                    sample_delta: 0,
263                    subsamples: vec![
264                        SubsSubsample {
265                            size: SubsSubsampleSize::U16(3991),
266                            priority: 0,
267                            discardable: false,
268                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
269                        },
270                        SubsSubsample {
271                            size: SubsSubsampleSize::U16(1362),
272                            priority: 0,
273                            discardable: false,
274                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
275                        },
276                        SubsSubsample {
277                            size: SubsSubsampleSize::U16(1443),
278                            priority: 0,
279                            discardable: false,
280                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
281                        },
282                        SubsSubsample {
283                            size: SubsSubsampleSize::U16(2952),
284                            priority: 0,
285                            discardable: false,
286                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
287                        },
288                        SubsSubsample {
289                            size: SubsSubsampleSize::U16(1955),
290                            priority: 0,
291                            discardable: false,
292                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
293                        },
294                        SubsSubsample {
295                            size: SubsSubsampleSize::U16(1525),
296                            priority: 0,
297                            discardable: false,
298                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
299                        },
300                        SubsSubsample {
301                            size: SubsSubsampleSize::U16(842),
302                            priority: 0,
303                            discardable: false,
304                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
305                        },
306                        SubsSubsample {
307                            size: SubsSubsampleSize::U16(1386),
308                            priority: 0,
309                            discardable: false,
310                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
311                        },
312                    ],
313                },
314                SubsEntry {
315                    sample_delta: 1,
316                    subsamples: vec![
317                        SubsSubsample {
318                            size: SubsSubsampleSize::U16(194),
319                            priority: 0,
320                            discardable: false,
321                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
322                        },
323                        SubsSubsample {
324                            size: SubsSubsampleSize::U16(171),
325                            priority: 0,
326                            discardable: false,
327                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
328                        },
329                        SubsSubsample {
330                            size: SubsSubsampleSize::U16(736),
331                            priority: 0,
332                            discardable: false,
333                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
334                        },
335                        SubsSubsample {
336                            size: SubsSubsampleSize::U16(734),
337                            priority: 0,
338                            discardable: false,
339                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
340                        },
341                        SubsSubsample {
342                            size: SubsSubsampleSize::U16(1470),
343                            priority: 0,
344                            discardable: false,
345                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
346                        },
347                        SubsSubsample {
348                            size: SubsSubsampleSize::U16(330),
349                            priority: 0,
350                            discardable: false,
351                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
352                        },
353                        SubsSubsample {
354                            size: SubsSubsampleSize::U16(150),
355                            priority: 0,
356                            discardable: false,
357                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
358                        },
359                        SubsSubsample {
360                            size: SubsSubsampleSize::U16(1014),
361                            priority: 0,
362                            discardable: false,
363                            codec_specific_parameters: 0u32.to_be_bytes().to_vec(),
364                        },
365                    ],
366                },
367            ],
368        };
369        let decoded = Subs::decode(&mut buf).expect("subs should decode successfully");
370        assert_eq!(subs, decoded);
371        let mut encoded = Vec::new();
372        subs.encode(&mut encoded)
373            .expect("encode should be successful");
374        assert_eq!(SUBS_COMPLEX, &encoded);
375    }
376}