Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/mp4a/
mod.rs

1use crate::*;
2
3// We're trying not to pollute the global namespace
4pub mod esds;
5pub use esds::Esds;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Mp4a {
10    pub audio: Audio,
11    pub esds: Esds,
12    pub btrt: Option<Btrt>,
13    pub taic: Option<Taic>,
14}
15
16impl Atom for Mp4a {
17    const KIND: FourCC = FourCC::new(b"mp4a");
18
19    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
20        let audio = Audio::decode(buf)?;
21
22        let mut btrt = None;
23        let mut esds = None;
24        let mut taic = None;
25
26        // The `esds` sits directly under `mp4a` (ISO-BMFF) or nested inside a
27        // QuickTime `wave` (siDecompressionParam) box (QTFF) — FFmpeg and many
28        // camera muxers emit the latter for AAC-in-MOV.
29        while let Some(atom) = Any::decode_maybe(buf)? {
30            match atom {
31                Any::Btrt(atom) => btrt = atom.into(),
32                Any::Esds(atom) => esds = atom.into(),
33                Any::Taic(atom) => taic = atom.into(),
34                Any::Unknown(kind, body) if kind == WAVE => {
35                    decode_wave(&body, &mut esds, &mut btrt)?;
36                }
37                unknown => Self::decode_unknown(&unknown)?,
38            }
39        }
40        skip_trailing_padding(buf);
41
42        Ok(Mp4a {
43            audio,
44            esds: esds.ok_or(Error::MissingBox(Esds::KIND))?,
45            btrt,
46            taic,
47        })
48    }
49
50    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
51        self.audio.encode(buf)?;
52        self.esds.encode(buf)?;
53        self.btrt.encode(buf)?;
54        self.taic.encode(buf)?;
55
56        Ok(())
57    }
58}
59
60/// QuickTime `wave` (siDecompressionParam) container FourCC.
61const WAVE: FourCC = FourCC::new(b"wave");
62
63/// Scan a QuickTime `wave` box body for the `esds` (and `btrt`) it wraps.
64///
65/// Every other child — the `frma` format box, the nested `mp4a` terminator
66/// stub, the trailing all-zero box — is skipped by size. Those are deliberately
67/// NOT typed-decoded: the nested `mp4a` in particular is a 4-byte stub that is
68/// not a valid audio sample entry, so decoding it as one would fail.
69fn decode_wave(mut wave: &[u8], esds: &mut Option<Esds>, btrt: &mut Option<Btrt>) -> Result<()> {
70    while let Some(header) = Header::decode_maybe(&mut wave)? {
71        // A size-to-end (`None`) or over-long child terminates the scan — the
72        // esds, if present, precedes any such terminator.
73        let size = match header.size {
74            Some(size) if size <= wave.remaining() => size,
75            _ => break,
76        };
77        let mut child = wave.slice(size);
78        if header.kind == Esds::KIND {
79            *esds = Some(Esds::decode_body(&mut child)?);
80        } else if header.kind == Btrt::KIND {
81            *btrt = Some(Btrt::decode_body(&mut child)?);
82        }
83        wave.advance(size);
84    }
85    Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_mp4a() {
94        let expected = Mp4a {
95            audio: Audio {
96                data_reference_index: 1,
97                channel_count: 2,
98                sample_size: 16,
99                sample_rate: 48000.into(),
100            },
101            esds: Esds {
102                es_desc: esds::EsDescriptor {
103                    es_id: 2,
104                    depends_on_es_id: None,
105                    url: None,
106                    ocr_es_id: None,
107                    stream_priority: 0,
108                    dec_config: esds::DecoderConfig {
109                        object_type_indication: 0x40,
110                        stream_type: 0x05,
111                        up_stream: 0,
112                        buffer_size_db: Default::default(),
113                        max_bitrate: 67695,
114                        avg_bitrate: 67695,
115                        dec_specific: Some(esds::DecoderSpecific {
116                            profile: 2,
117                            freq_index: 4,
118                            chan_conf: 2,
119                            raw: vec![0x12, 0x10],
120                        }),
121                    },
122                    sl_config: esds::SLConfig::default(),
123                },
124            },
125            btrt: Some(Btrt {
126                buffer_size_db: 1,
127                max_bitrate: 2,
128                avg_bitrate: 3,
129            }),
130            taic: None,
131        };
132        let mut buf = Vec::new();
133        expected.encode(&mut buf).unwrap();
134
135        let mut buf = buf.as_ref();
136        let decoded = Mp4a::decode(&mut buf).unwrap();
137        assert_eq!(decoded, expected);
138    }
139
140    // Build `[size:u32][fourcc][body]`.
141    fn atom_box(fourcc: &[u8; 4], body: &[u8]) -> Vec<u8> {
142        let mut v = Vec::with_capacity(8 + body.len());
143        v.extend_from_slice(&((8 + body.len()) as u32).to_be_bytes());
144        v.extend_from_slice(fourcc);
145        v.extend_from_slice(body);
146        v
147    }
148
149    // QuickTime (QTFF) `mp4a` sample entry: the AAC `esds` is nested inside a
150    // `wave` (siDecompressionParam) box rather than a direct child of `mp4a`.
151    // FFmpeg and camera muxers (MotionCam, etc.) emit MOV audio this way.
152    #[test]
153    fn test_mp4a_quicktime_wave() {
154        let audio = Audio {
155            data_reference_index: 1,
156            channel_count: 2,
157            sample_size: 16,
158            sample_rate: 48000.into(),
159        };
160        let esds = Esds {
161            es_desc: esds::EsDescriptor {
162                es_id: 2,
163                depends_on_es_id: None,
164                url: None,
165                ocr_es_id: None,
166                stream_priority: 0,
167                dec_config: esds::DecoderConfig {
168                    object_type_indication: 0x40,
169                    stream_type: 0x05,
170                    up_stream: 0,
171                    buffer_size_db: Default::default(),
172                    max_bitrate: 67695,
173                    avg_bitrate: 67695,
174                    dec_specific: Some(esds::DecoderSpecific {
175                        profile: 2,
176                        freq_index: 4,
177                        chan_conf: 2,
178                        raw: vec![0x12, 0x10],
179                    }),
180                },
181                sl_config: esds::SLConfig::default(),
182            },
183        };
184
185        let btrt = Btrt {
186            buffer_size_db: 1,
187            max_bitrate: 2,
188            avg_bitrate: 3,
189        };
190
191        // Encode the pieces the QuickTime layout wraps.
192        let mut audio_bytes = Vec::new();
193        audio.encode(&mut audio_bytes).unwrap();
194        let mut esds_box = Vec::new();
195        esds.encode(&mut esds_box).unwrap();
196        let mut btrt_box = Vec::new();
197        btrt.encode(&mut btrt_box).unwrap();
198
199        // wave { frma('mp4a'), mp4a stub (4 zero bytes — NOT a sample entry),
200        //        esds, btrt, all-zero terminator }
201        let mut wave_body = Vec::new();
202        wave_body.extend_from_slice(&atom_box(b"frma", b"mp4a"));
203        wave_body.extend_from_slice(&atom_box(b"mp4a", &[0, 0, 0, 0]));
204        wave_body.extend_from_slice(&esds_box);
205        wave_body.extend_from_slice(&btrt_box);
206        wave_body.extend_from_slice(&[0, 0, 0, 8, 0, 0, 0, 0]);
207
208        // mp4a { audio-header, wave }
209        let mut mp4a_body = audio_bytes;
210        mp4a_body.extend_from_slice(&atom_box(b"wave", &wave_body));
211        let mp4a = atom_box(b"mp4a", &mp4a_body);
212
213        let mut buf = mp4a.as_slice();
214        let decoded = Mp4a::decode(&mut buf).unwrap();
215        assert_eq!(decoded.audio, audio);
216        assert_eq!(decoded.esds, esds);
217        // A `btrt` nested alongside the esds inside `wave` is hoisted too.
218        assert_eq!(decoded.btrt, Some(btrt));
219    }
220
221    #[test]
222    fn test_mp4a_with_taic() {
223        let expected = Mp4a {
224            audio: Audio {
225                data_reference_index: 1,
226                channel_count: 2,
227                sample_size: 16,
228                sample_rate: 48000.into(),
229            },
230            esds: Esds {
231                es_desc: esds::EsDescriptor {
232                    es_id: 2,
233                    depends_on_es_id: None,
234                    url: None,
235                    ocr_es_id: None,
236                    stream_priority: 0,
237                    dec_config: esds::DecoderConfig {
238                        object_type_indication: 0x40,
239                        stream_type: 0x05,
240                        up_stream: 0,
241                        buffer_size_db: Default::default(),
242                        max_bitrate: 67695,
243                        avg_bitrate: 67695,
244                        dec_specific: Some(esds::DecoderSpecific {
245                            profile: 2,
246                            freq_index: 4,
247                            chan_conf: 2,
248                            raw: vec![0x12, 0x10],
249                        }),
250                    },
251                    sl_config: esds::SLConfig::default(),
252                },
253            },
254            btrt: None,
255            taic: Some(Taic {
256                time_uncertainty: 1,
257                clock_resolution: 2,
258                clock_drift_rate: 4,
259                clock_type: ClockType::CanSync,
260            }),
261        };
262        let mut buf = Vec::new();
263        expected.encode(&mut buf).unwrap();
264
265        let mut buf = buf.as_ref();
266        let decoded = Mp4a::decode(&mut buf).unwrap();
267        assert_eq!(decoded, expected);
268    }
269}