Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
spatial.rs

1use crate::*;
2
3/// The projection and stereo boxes of a visual sample entry. Entries hold it
4/// as `spatial: Option<Box<Spatial>>`, since it is large and usually absent.
5///
6/// Each sample entry writes these boxes just before `pasp`: [Spherical Video
7/// V2] requires `st3d` and `sv3d` ahead of the optional `clap` and `pasp`, and
8/// ISO/IEC 14496-12 ยง12.1.4.1 wants `pasp` and `clap` after the boxes of
9/// other specifications. `st3d` comes first, as V2 asks, and in any case
10/// before `vexu` and `hfov`: a reader may refuse an `st3d` that arrives after
11/// another box has set the stereo layout. [Apple] does not constrain where
12/// `vexu` and `hfov` go.
13///
14/// None of these boxes or their children are in [`Any`]. They only mean
15/// something inside a sample entry, and several reuse the fourccs of
16/// unrelated boxes (two different `proj`, two different `equi`, and ISO's
17/// sub-track `stri`). Their containers decode children by header instead.
18///
19/// A box that repeats, or does not decode, never fails the sample entry: its
20/// parent keeps it verbatim, as an [`Any::Unknown`] in `unknown`, and logs a
21/// warning. Children this crate does not model are kept there too. On encode,
22/// a kept box of a modelled kind goes back at that kind's position, because
23/// readers may depend on the order of children.
24///
25/// A writer may set both families, describing the same projection: a reader
26/// that understands both may use either one. The members' docs say what a
27/// writer should set.
28///
29/// [Spherical Video V2]: https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md
30/// [Apple]: https://developer.apple.com/av-foundation/Stereo-Video-ISOBMFF-Extensions.pdf
31#[derive(Debug, Clone, PartialEq, Default)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[cfg_attr(feature = "serde", serde(default))]
34pub struct Spatial {
35    pub st3d: Option<St3d>,
36    pub sv3d: Option<Sv3d>,
37    pub vexu: Option<Vexu>,
38    pub hfov: Option<Hfov>,
39    /// `st3d`, `sv3d`, `vexu` or `hfov` boxes that repeat or did not decode.
40    pub unknown: Vec<Any>,
41}
42
43impl Eq for Spatial {}
44
45impl Spatial {
46    /// Keep `atom` in `spatial` if it is one of these boxes, creating it on the
47    /// first one; otherwise report it as unknown to the sample entry `T`.
48    pub(crate) fn decode_child<T: Atom>(
49        spatial: &mut Option<Box<Spatial>>,
50        atom: Any,
51    ) -> Result<()> {
52        match atom {
53            Any::Unknown(kind @ (St3d::KIND | Sv3d::KIND | Vexu::KIND | Hfov::KIND), body) => {
54                let spatial: &mut Spatial = spatial.get_or_insert_with(Default::default);
55                let unknown = &mut spatial.unknown;
56                match kind {
57                    St3d::KIND => decode_slot(T::KIND, &mut spatial.st3d, &body, unknown),
58                    Sv3d::KIND => decode_slot(T::KIND, &mut spatial.sv3d, &body, unknown),
59                    Vexu::KIND => decode_slot(T::KIND, &mut spatial.vexu, &body, unknown),
60                    _ => decode_slot(T::KIND, &mut spatial.hfov, &body, unknown),
61                }
62                Ok(())
63            }
64            atom => T::decode_unknown(&atom),
65        }
66    }
67}
68
69impl Encode for Spatial {
70    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
71        self.st3d.encode(buf)?;
72        encode_kept(&self.unknown, |kind| kind == St3d::KIND, buf)?;
73        self.sv3d.encode(buf)?;
74        encode_kept(&self.unknown, |kind| kind == Sv3d::KIND, buf)?;
75        self.vexu.encode(buf)?;
76        encode_kept(&self.unknown, |kind| kind == Vexu::KIND, buf)?;
77        self.hfov.encode(buf)?;
78        encode_kept(&self.unknown, |kind| kind == Hfov::KIND, buf)?;
79        encode_kept(
80            &self.unknown,
81            |kind| !matches!(kind, St3d::KIND | Sv3d::KIND | Vexu::KIND | Hfov::KIND),
82            buf,
83        )
84    }
85}
86
87/// The version and flags of every FullBox here, which their specs define as
88/// 0. Anything else is a layout this crate does not know, so the box does not
89/// decode and its parent keeps it verbatim.
90#[derive(Debug, Clone, Copy, Default)]
91pub(crate) struct FullBoxV0;
92
93impl Ext for FullBoxV0 {
94    fn encode(&self) -> Result<u32> {
95        Ok(0)
96    }
97
98    fn decode(v: u32) -> Result<Self> {
99        match v {
100            0 => Ok(Self),
101            v if v >> 24 != 0 => Err(Error::UnknownVersion((v >> 24) as u8)),
102            _ => Err(Error::Unsupported("non-zero FullBox flags")),
103        }
104    }
105}
106
107/// Spherical Video V2: "Parsers should ignore extra bytes at the end of a
108/// box", as later revisions may append fields to it.
109pub(super) fn ignore_trailing<B: Buf>(kind: FourCC, buf: &mut B) {
110    if buf.has_remaining() {
111        tracing::warn!(%kind, bytes = buf.remaining(), "ignoring trailing bytes");
112        buf.advance(buf.remaining());
113    }
114}
115
116/// Split a container's body into its children, passing the kind and body of
117/// each to `child`.
118pub(super) fn decode_children<B: Buf>(
119    buf: &mut B,
120    mut child: impl FnMut(FourCC, &[u8]),
121) -> Result<()> {
122    while let Some(header) = Header::decode_maybe(buf)? {
123        let size = header.size.unwrap_or(buf.remaining());
124        if size > buf.remaining() {
125            return Err(Error::OutOfBounds);
126        }
127        child(header.kind, buf.slice(size));
128        buf.advance(size);
129    }
130    // Tolerate a sub-header padding remainder (e.g. a QuickTime zero terminator).
131    skip_trailing_padding(buf);
132    Ok(())
133}
134
135/// Decode the child `T` from `body` into `slot`, or keep it verbatim in
136/// `unknown` if it repeats or does not decode.
137pub(super) fn decode_slot<T: Atom>(
138    parent: FourCC,
139    slot: &mut Option<T>,
140    body: &[u8],
141    unknown: &mut Vec<Any>,
142) {
143    decode_variant(parent, slot, body, unknown, |atom: T| atom)
144}
145
146/// [`decode_slot`] for a slot that holds one of several boxes, each wrapped
147/// by `wrap`.
148pub(super) fn decode_variant<T: Atom, S>(
149    parent: FourCC,
150    slot: &mut Option<S>,
151    body: &[u8],
152    unknown: &mut Vec<Any>,
153    wrap: impl FnOnce(T) -> S,
154) {
155    let decoded = match slot {
156        Some(_) => Err(Error::DuplicateBox(T::KIND)),
157        None => decode_exact::<T>(body),
158    };
159    match decoded {
160        Ok(atom) => *slot = Some(wrap(atom)),
161        Err(err) => keep_invalid(parent, T::KIND, body, err, unknown),
162    }
163}
164
165fn decode_exact<T: Atom>(mut body: &[u8]) -> Result<T> {
166    let atom = T::decode_body(&mut body)?;
167    match body.is_empty() {
168        true => Ok(atom),
169        false => Err(Error::UnderDecode(T::KIND)),
170    }
171}
172
173/// Keep a child that could not be used verbatim, with a warning.
174pub(super) fn keep_invalid(
175    parent: FourCC,
176    kind: FourCC,
177    body: &[u8],
178    err: Error,
179    unknown: &mut Vec<Any>,
180) {
181    tracing::warn!(%kind, %parent, %err, "keeping spatial box verbatim");
182    unknown.push(Any::Unknown(kind, body.to_vec()));
183}
184
185/// Write the kept children whose type matches `kind`, in their original order.
186pub(super) fn encode_kept<B: BufMut>(
187    unknown: &[Any],
188    kind: impl Fn(FourCC) -> bool,
189    buf: &mut B,
190) -> Result<()> {
191    unknown
192        .iter()
193        .filter(|atom| kind(atom.kind()))
194        .try_for_each(|atom| atom.encode(buf))
195}
196
197// Generates `decode_body`/`encode_body` for a container whose children each
198// fill one optional field. Each field is written followed by the kept boxes of
199// its kind, then the other kept children, then the optional `last` field.
200/* example:
201spatial_children! { must: Must, stri: Stri, hero: Hero }
202spatial_children! { must: Must, eyes: Eyes, pack: Pack; last proj: VexuProj }
203*/
204macro_rules! spatial_children {
205    ($($field:ident: $atom:ident),* $(; last $last:ident: $last_atom:ident)? $(,)?) => {
206        fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
207            let mut atom = Self::default();
208            decode_children(buf, |kind, body| match kind {
209                $($atom::KIND => decode_slot(Self::KIND, &mut atom.$field, body, &mut atom.unknown),)*
210                $($last_atom::KIND => decode_slot(Self::KIND, &mut atom.$last, body, &mut atom.unknown),)?
211                kind => atom.unknown.push(Any::Unknown(kind, body.to_vec())),
212            })?;
213            Ok(atom)
214        }
215
216        fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
217            $(
218                self.$field.encode(buf)?;
219                encode_kept(&self.unknown, |kind| kind == $atom::KIND, buf)?;
220            )*
221            encode_kept(
222                &self.unknown,
223                |kind| !matches!(kind, $($atom::KIND)|* $(| $last_atom::KIND)?),
224                buf,
225            )?;
226            $(
227                self.$last.encode(buf)?;
228                encode_kept(&self.unknown, |kind| kind == $last_atom::KIND, buf)?;
229            )?
230            Ok(())
231        }
232    };
233}
234
235pub(super) use spatial_children;