truehd 0.7.1

Research implementation of Dolby TrueHD parser/decoder
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
use anyhow::{Result, bail};
use std::fmt::Display;

/// Frame extraction from audio bitstreams.
///
/// Provides the [`Extractor`](extract::Extractor) for finding sync patterns and
/// extracting individual [`Frame`](extract::Frame) objects from continuous bitstream data.
pub mod extract;

/// Frame parsing into structured access units.
///
/// Provides the [`Parser`](parse::Parser) for converting raw frames into
/// [`AccessUnit`](crate::structs::access_unit::AccessUnit) objects with parsed metadata.
pub mod parse;

/// Audio decoding to PCM samples.
///
/// Provides the [`Decoder`](decode::Decoder) for converting access units into
/// [`DecodedAccessUnit`](decode::DecodedAccessUnit) objects containing PCM audio data.
pub mod decode;

pub const EXAMPLE_DATA: &[u8] = &[
    0x01, 0x10, 0x00, 0x01, 0x00, 0x23, 0x00, 0x45, 0x00, 0x16, 0x00, 0x19, 0x00, 0x11, 0x80, 0x00,
    0xF0, 0x2A, 0xFF, 0xAC, 0xF8, 0x72, 0x6F, 0xBA, 0x00, 0x00, 0x80, 0x01, 0xB7, 0x52, 0x00, 0x00,
    0x00, 0x00, 0x80, 0x80, 0x10, 0x14, 0x03, 0x80, 0x3F, 0x1F, 0xE3, 0x07, 0xE3, 0x00, 0x52, 0x98,
    0xB0, 0x18, 0x03, 0xF0, 0xF1, 0xEA, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x02, 0x09, 0x52, 0x80,
    0x00, 0x00, 0x00, 0x02, 0xB4, 0x44, 0x01, 0xE8, 0xC4, 0x40, 0x88, 0xD1, 0xFE, 0x91, 0x00, 0x63,
    0x03, 0xE9, 0x18, 0x33, 0x86, 0x20, 0x68, 0xFF, 0xCB, 0x6E, 0xDB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB7,
    0x80, 0x00, 0x64, 0xF9, 0x50, 0x0A, 0x00, 0x00, 0x70, 0x07, 0x91, 0x40, 0x48, 0x00, 0x11, 0x3D,
    0xDB, 0xEF, 0xF3, 0xDE, 0xD0, 0x00, 0xD5, 0x04,
];

/// One access unit of an FBB (Meridian / DVD-Audio) stream, six-channel independent with a
/// two-channel downmix. Regression fixture for reading FBB at all, which bailed with
/// `unimplemented!` until 0.6.4.
pub const EXAMPLE_DATA_FBB: &[u8] = &[
    0xF0, 0xF1, 0xF1, 0xFD, 0xF8, 0x72, 0x6F, 0xBB, 0x22, 0x00, 0x00, 0x11, 0xB7, 0x52, 0x40, 0x00,
    0x00, 0x00, 0x82, 0xA1, 0x20, 0x0D, 0x56, 0x3F, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0xA7, 0x63,
    0x30, 0x45, 0x20, 0xDF, 0xF1, 0xEA, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x02, 0x12, 0xB5, 0x80,
    0x00, 0x00, 0x00, 0x02, 0x16, 0x48, 0x79, 0xD3, 0xC4, 0xDE, 0x6F, 0x81, 0xCE, 0x17, 0xE4, 0x11,
    0x44, 0x21, 0x84, 0x80, 0x09, 0x25, 0x80, 0x00, 0x1F, 0xFF, 0x7A, 0xB9, 0xAE, 0xDB, 0xB2, 0xCB,
    0x9D, 0xE5, 0xBB, 0xDA, 0x0B, 0xA2, 0x24, 0xDA, 0x45, 0xD4, 0xE8, 0xDF, 0x1A, 0x1D, 0x3E, 0xF2,
    0x5F, 0xCC, 0x4C, 0xE4, 0xC5, 0x18, 0x30, 0xCA, 0x2A, 0x81, 0x7D, 0x89, 0x10, 0x0C, 0xF6, 0x01,
    0x7F, 0xC6, 0x80, 0x50, 0x7F, 0xED, 0x1D, 0x3F, 0x85, 0x30, 0x5F, 0xFE, 0xA1, 0x73, 0xD8, 0x05,
    0xFE, 0xDA, 0x01, 0xB1, 0xFF, 0x80, 0x13, 0x73, 0x7E, 0xAD, 0xDE, 0x21, 0xD2, 0x87, 0x64, 0x65,
    0xB4, 0xAA, 0x55, 0xA2, 0x14, 0xA4, 0xF5, 0x31, 0x60, 0xD2, 0x68, 0x33, 0x96, 0x09, 0x07, 0x52,
    0xB9, 0x90, 0xAE, 0x95, 0x31, 0x1D, 0xD4, 0x48, 0x56, 0x8A, 0xD6, 0x00, 0xE6, 0xC4, 0xF1, 0xEA,
    0x00, 0x00, 0x25, 0x50, 0x00, 0x00, 0x04, 0x14, 0xB5, 0x80, 0x00, 0x00, 0x00, 0x02, 0x10, 0x62,
    0x0A, 0xCE, 0x58, 0xD9, 0x10, 0x88, 0xFF, 0xB8, 0x10, 0x38, 0x08, 0xAA, 0x92, 0x87, 0xF9, 0x38,
    0x04, 0x67, 0xBA, 0x03, 0x04, 0x8D, 0xF0, 0x20, 0xE1, 0x33, 0xF7, 0xE7, 0xE0, 0x77, 0x7E, 0xD0,
    0x30, 0x2D, 0xC0, 0x07, 0xED, 0xE1, 0x00, 0x47, 0xFF, 0x03, 0x89, 0x61, 0xFC, 0x00, 0x02, 0x00,
    0x84, 0x01, 0x02, 0x7F, 0xCF, 0x94, 0x82, 0x70, 0xC2, 0x18, 0xC0, 0x03, 0x0C, 0x00, 0x00, 0xC0,
    0x00, 0x18, 0x00, 0x00, 0xE3, 0x93, 0x8E, 0x16, 0xEA, 0xAC, 0x88, 0x55, 0x59, 0xE3, 0x1C, 0x9D,
    0xB0, 0xB9, 0x39, 0x65, 0x51, 0x2A, 0xEE, 0x8A, 0xAD, 0x3E, 0xA4, 0x6A, 0x60, 0x6A, 0x8C, 0x02,
    0xD4, 0x23, 0xA9, 0x6A, 0x18, 0x1C, 0x2D, 0x31, 0xE5, 0x6D, 0xA0, 0x72, 0xA8, 0xE6, 0x7C, 0x28,
    0x71, 0xFA, 0xC6, 0xD7, 0xCC, 0x56, 0xE9, 0x84, 0x9A, 0x40, 0x4A, 0x3A, 0x44, 0x4B, 0xBE, 0x3B,
    0x73, 0xE2, 0xBB, 0x34, 0x31, 0xA1, 0x41, 0x72, 0xD6, 0xA4, 0x40, 0x33, 0xD8, 0x05, 0xFF, 0x0A,
    0x01, 0x11, 0xFF, 0xF4, 0x74, 0xFE, 0x15, 0x01, 0x82, 0x33, 0xD8, 0x05, 0xFF, 0x4A, 0x01, 0x11,
    0xFF, 0xB0, 0x87, 0x3C, 0x78, 0x5F, 0xE9, 0x40, 0x34, 0x7F, 0xF9, 0x00, 0x2E, 0x13, 0x3C, 0x78,
    0x5F, 0xE1, 0x40, 0x4C, 0x7F, 0xEA, 0x38, 0x6F, 0x15, 0x81, 0x7F, 0xF9, 0x01, 0x1C, 0x0D, 0x3E,
    0x1A, 0xFF, 0x1B, 0x5A, 0x0A, 0x7C, 0x5C, 0xCE, 0x5B, 0x6C, 0x2E, 0xCA, 0x0B, 0xD7, 0x59, 0x4B,
    0x8D, 0xA8, 0x2B, 0x06, 0x67, 0x8A, 0x9E, 0x86, 0xCA, 0x5B, 0xA5, 0xB9, 0xF6, 0x85, 0x69, 0x5B,
    0x94, 0x99, 0x4C, 0x64, 0x38, 0xBD, 0xC3, 0xB8, 0x4B, 0x43, 0x48, 0x2D, 0xC2, 0xE7, 0x82, 0x92,
    0x97, 0x5E, 0x72, 0x26, 0xC2, 0xB2, 0x06, 0xAC, 0x81, 0x96, 0x13, 0x91, 0x75, 0xD3, 0x91, 0x85,
    0x69, 0x81, 0x35, 0x31, 0xD8, 0x8A, 0x49, 0xB0, 0x82, 0x29, 0xD0, 0x8A, 0x08, 0x72, 0x22, 0x70,
    0x2C, 0x10, 0x33, 0x26, 0x13, 0x2D, 0x0E, 0x84, 0xC9, 0x82, 0x91, 0x62, 0x00, 0xE4, 0x62, 0x00,
    0xA4, 0x88,
];

/// One access unit of another FBB stream, six-channel copy-of two-channel. It sets
/// extra_channel_meaning_present and carries substream_info 0x05, both of which used to be
/// judged by FBA-only rules, so no frame came out. Regression fixture for FBB-specific
/// major sync info.
pub const EXAMPLE_DATA_FBB_UNEXTRACTABLE: &[u8] = &[
    0xA0, 0x4B, 0xF3, 0x48, 0xF8, 0x72, 0x6F, 0xBB, 0x2F, 0x0F, 0x00, 0x01, 0xB7, 0x52, 0x40, 0x00,
    0x00, 0x00, 0x80, 0xC2, 0x10, 0x05, 0x56, 0x03, 0x00, 0x00, 0x80, 0x80, 0x00, 0x1B, 0x65, 0x38,
    0x30, 0x3A, 0xF1, 0xEA, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x02, 0x12, 0xB5, 0x80, 0x00, 0x00,
    0x00, 0x02, 0x16, 0x44, 0x51, 0x2F, 0x80, 0x43, 0x09, 0x00, 0x14, 0x4B, 0x00, 0x00, 0x3F, 0xFE,
    0xDA, 0xA1, 0x31, 0xB6, 0xC6, 0xEA, 0x0C, 0xCB, 0x6D, 0xD7, 0xF8, 0x1B, 0x49, 0x3E, 0x1C, 0xF9,
    0xC9, 0xB9, 0x48, 0xA8, 0xB1, 0xA4, 0xA7, 0x0A, 0xF5, 0x73, 0x09, 0xBA, 0xC9, 0x8F, 0x12, 0x01,
    0xE9, 0x52, 0x20, 0x19, 0xE3, 0xC2, 0xFF, 0x7A, 0x01, 0xA3, 0xFF, 0x02, 0x0C, 0xF6, 0x01, 0x7F,
    0xB6, 0x80, 0x6C, 0x7F, 0xE0, 0x04, 0x72, 0xB6, 0xF3, 0x22, 0x7D, 0xBF, 0xA7, 0x0E, 0xFB, 0xBB,
    0xEB, 0xA5, 0x37, 0x51, 0xD3, 0x7C, 0xDE, 0x3B, 0xF4, 0xCF, 0x99, 0xA5, 0x4C, 0x7D, 0x71, 0x44,
    0x67, 0x0B, 0x84, 0x00, 0xE3, 0x66,
];

pub const MAX_PRESENTATIONS: usize = 4;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PresentationMap {
    pub masks: [u8; MAX_PRESENTATIONS],
}

impl PresentationMap {
    /// The map a major sync declares, derived by the rules of its own syntax.
    pub fn for_format_sync(
        format_sync: u32,
        substream_info: u8,
        extended_substream_info: u8,
    ) -> Self {
        if format_sync == crate::structs::sync::MAJOR_SYNC_FBB {
            Self::with_fbb_substream_info(substream_info)
        } else {
            Self::with_substream_info(substream_info, extended_substream_info)
        }
    }

    /// The FBA derivation: `substream_info` bits 2-7 and `extended_substream_info` select
    /// the 2-, 6-, 8- and 16-channel presentations.
    pub fn with_substream_info(substream_info: u8, extended_substream_info: u8) -> Self {
        Self {
            masks: [
                1,
                (substream_info >> 2) & 3,
                (substream_info >> 4) & 7,
                ((substream_info >> 4) & 8) | (7 ^ (7 >> (extended_substream_info & 3))),
            ],
        }
    }

    /// The FBB derivation, which shares nothing with the FBA one.
    ///
    /// Only the low nibble of `substream_info` is defined, and within it only bit 3 says
    /// anything about presentations: set, substream 1 is also to be decoded and there is a
    /// second presentation over substreams 0 and 1; clear, substream 0 alone is decodable
    /// and the stream declares exactly one presentation. There is no `extended_substream_info`
    /// and no 8- or 16-channel presentation.
    pub fn with_fbb_substream_info(substream_info: u8) -> Self {
        Self {
            masks: [1, if substream_info & 8 != 0 { 3 } else { 0 }, 0, 0],
        }
    }

    pub fn presentation_type_by_index(&self, index: usize) -> PresentationType {
        if index >= self.masks.len() {
            return PresentationType::Invalid;
        }
        let this_mask = self.masks[index];

        if this_mask >> index != 0 {
            if let Some(down_i) = (index + 1..self.masks.len())
                .find(|&i| self.masks[i] >> i != 0 && (self.masks[i] >> index) & 1 != 0)
            {
                return PresentationType::DownmixOf(down_i);
            }
            return PresentationType::Independent;
        }

        if let Some(copy_i) = (0..index).rev().find(|&i| this_mask >> i != 0) {
            return PresentationType::CopyOf(copy_i);
        }

        PresentationType::Invalid
    }

    pub fn max_independent_presentation(&self) -> Option<usize> {
        self.masks
            .iter()
            .enumerate()
            .rev()
            .find(|&(i, &mask)| mask >> i != 0)
            .map(|(i, _)| i)
    }

    pub fn substream_mask_by_index(&self, index: usize) -> u8 {
        if index >= MAX_PRESENTATIONS {
            0
        } else {
            self.masks[index]
        }
    }

    pub fn substream_mask_by_required_presentations(
        &self,
        required_presentations: &[bool; MAX_PRESENTATIONS],
    ) -> u8 {
        required_presentations
            .iter()
            .enumerate()
            .fold(0, |mask, (i, &required)| {
                if required {
                    // A presentation the stream does not carry resolves to the highest
                    // one it does, so those substreams are still needed
                    let index = match self.presentation_type_by_index(i) {
                        PresentationType::Invalid => self.max_independent_presentation(),
                        _ => Some(i),
                    };
                    mask | index.map_or(0, |i| self.substream_mask_by_index(i))
                } else {
                    mask
                }
            })
    }

    pub fn effective_presentations(
        &self,
        required_presentations: &[bool; MAX_PRESENTATIONS],
    ) -> Result<[bool; MAX_PRESENTATIONS]> {
        let mut presentations = [false; MAX_PRESENTATIONS];

        for (i, _) in required_presentations
            .iter()
            .enumerate()
            .filter(|&(_, &required)| required)
        {
            match self.presentation_type_by_index(i) {
                // A stream need not carry four presentations. Asking for one it does
                // not have decodes the highest available rather than nothing
                PresentationType::Invalid => {
                    let Some(max_independent) = self.max_independent_presentation() else {
                        bail!("No presentation is available");
                    };
                    presentations[max_independent] = true;
                }
                PresentationType::CopyOf(copy_i) => presentations[copy_i] = true,
                _ => presentations[i] = true,
            }
        }

        Ok(presentations)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PresentationType {
    Invalid,
    CopyOf(usize),
    DownmixOf(usize),
    Independent,
}

impl Display for PresentationType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PresentationType::Invalid => write!(f, "Invalid"),
            PresentationType::CopyOf(i) => write!(f, "Copy of presentation {i}"),
            PresentationType::DownmixOf(i) => write!(f, "Downmix of presentation {i}"),
            PresentationType::Independent => write!(f, "Independent"),
        }
    }
}

#[test]
fn test_presentation_map() {
    let map = PresentationMap::with_substream_info(0b11001100, 0b00000001);
    assert_eq!(map.max_independent_presentation().unwrap(), 3);
    assert_eq!(map.masks, [1, 3, 4, 12]);

    assert_eq!(
        map.presentation_type_by_index(0),
        PresentationType::DownmixOf(1)
    );
    assert_eq!(
        map.presentation_type_by_index(1),
        PresentationType::Independent
    );
    assert_eq!(
        map.presentation_type_by_index(2),
        PresentationType::DownmixOf(3)
    );

    let map = PresentationMap::with_substream_info(0b01011000, 0b00000000);
    assert_eq!(map.max_independent_presentation().unwrap(), 2);
    assert_eq!(map.masks, [1, 2, 5, 0]);

    assert_eq!(
        map.presentation_type_by_index(0),
        PresentationType::DownmixOf(2)
    );
    assert_eq!(
        map.presentation_type_by_index(1),
        PresentationType::Independent
    );
}

/// The two derived presentation kinds point in opposite directions: a presentation can only
/// be a copy of one with fewer channels, so a `CopyOf` names a LOWER index and a `DownmixOf`
/// a HIGHER one.
#[test]
fn copy_of_points_down_and_downmix_of_points_up() {
    // EXAMPLE_DATA: substream_info 0b00010100, one substream shared by three presentations
    let fba = PresentationMap::with_substream_info(0b00010100, 0);
    assert_eq!(fba.masks, [1, 1, 1, 0]);
    assert_eq!(fba.max_independent_presentation().unwrap(), 0);
    assert_eq!(
        fba.presentation_type_by_index(0),
        PresentationType::Independent
    );
    for i in [1, 2] {
        match fba.presentation_type_by_index(i) {
            PresentationType::CopyOf(j) => {
                assert!(
                    j < i,
                    "a copy must name a lower presentation, got {j} for {i}"
                )
            }
            other => panic!("presentation {i} should be a copy, got {other:?}"),
        }
    }
    assert_eq!(fba.presentation_type_by_index(3), PresentationType::Invalid);

    // EXAMPLE_DATA_FBB: substream_info 0b00001101, a two-channel downmix of the six-channel
    let fbb = PresentationMap::with_fbb_substream_info(0b00001101);
    assert_eq!(fbb.masks, [1, 3, 0, 0]);
    assert_eq!(fbb.max_independent_presentation().unwrap(), 1);
    match fbb.presentation_type_by_index(0) {
        PresentationType::DownmixOf(j) => {
            assert!(j > 0, "a downmix must name a higher presentation, got {j}")
        }
        other => panic!("presentation 0 should be a downmix, got {other:?}"),
    }
    assert_eq!(
        fbb.presentation_type_by_index(1),
        PresentationType::Independent
    );
}

/// FBB derives its presentations from bit 3 of `substream_info` alone: set, substream 1 is
/// also to be decoded and there is a second presentation; clear, the stream declares
/// exactly one. The FBA derivation would instead read `substream_info & 0x0C` as a second
/// presentation that is a copy of the first, and 0x05 is a value only FBB has.
#[test]
fn the_fbb_presentation_map_is_not_the_fba_one() {
    let one = PresentationMap::with_fbb_substream_info(0x05);
    assert_eq!(one.masks, [1, 0, 0, 0]);
    assert_eq!(one.max_independent_presentation(), Some(0));
    assert_eq!(
        one.presentation_type_by_index(0),
        PresentationType::Independent
    );
    assert_eq!(one.presentation_type_by_index(1), PresentationType::Invalid);

    // so a decode that asks for the highest presentation gets the only one there is
    assert_eq!(
        one.effective_presentations(&[false, false, false, true])
            .unwrap(),
        [true, false, false, false]
    );

    let two = PresentationMap::with_fbb_substream_info(0x0D);
    assert_eq!(two.masks, [1, 3, 0, 0]);
    assert_eq!(two.max_independent_presentation(), Some(1));

    // the FBA derivation of the same value, which is what used to be applied
    assert_eq!(
        PresentationMap::with_substream_info(0x05, 0).masks,
        [1, 1, 0, 0]
    );
    assert_eq!(
        PresentationMap::for_format_sync(crate::structs::sync::MAJOR_SYNC_FBB, 0x05, 0).masks,
        one.masks
    );
}

/// Presentation masks are NOT nested. It is tempting to assume presentation *n* reads
/// substreams 0..=n, but `substream_info = 0b11001100` with extended bit 0 gives masks
/// `[1, 3, 4, 12]`, where `3 & 4 == 0`: presentation 2 uses substream 2 alone and shares
/// nothing with presentation 1.
#[test]
fn masks_need_not_be_nested() {
    let m = PresentationMap::with_substream_info(0b11001100, 0b00000001);
    assert_eq!(m.masks, [1, 3, 4, 12]);
    assert_eq!(m.masks[1] & m.masks[2], 0, "disjoint, not nested");

    // where they do overlap it is genuine containment, so neither rule holds universally
    assert_eq!(m.masks[0] & m.masks[1], m.masks[0]);
}

/// A stream need not carry four presentations. Asking for one it does not have
/// resolved to nothing between 0.5.0 and 0.6.2, so the caller received no audio
/// while the log claimed a fallback had been applied.
#[test]
fn absent_presentation_resolves_to_the_highest_available() {
    // masks [1, 2, 5, 0]: presentation 3 is absent, 2 is the highest available.
    let map = PresentationMap::with_substream_info(0b01011000, 0b00000000);
    assert_eq!(map.presentation_type_by_index(3), PresentationType::Invalid);

    let required = [false, false, false, true];
    assert_eq!(
        map.effective_presentations(&required).unwrap(),
        [false, false, true, false]
    );
    assert_eq!(
        map.substream_mask_by_required_presentations(&required),
        map.substream_mask_by_index(2)
    );
}

#[test]
fn every_presentation_of_the_example_stream_decodes() {
    use decode::Decoder;
    use extract::Extractor;
    use parse::Parser;

    for presentation in 0..MAX_PRESENTATIONS {
        let mut extractor = Extractor::default();
        extractor.push_bytes(EXAMPLE_DATA);

        let mut parser = Parser::default();
        let mut decoder = Decoder::default();
        let mut frames = 0;

        for frame in extractor.flatten() {
            let access_unit = parser.parse(&frame).expect("example data must parse");
            let decoded = decoder
                .decode_presentation(&access_unit, presentation)
                .unwrap_or_else(|e| panic!("presentation {presentation}: {e}"));
            assert!(decoded.sample_length > 0, "presentation {presentation}");
            frames += 1;
        }

        assert_eq!(frames, 2, "presentation {presentation}");
    }
}

/// Each accumulator sums the substreams its own presentation is made of. This stream
/// carries one substream and declares presentations 1 and 2 as copies of presentation 0,
/// so all three decoders buffer the same substream and reach the same depth. The
/// 16-channel presentation does not exist and stays empty.
#[test]
fn fifo_depth_follows_the_presentation_masks() {
    use extract::Extractor;
    use parse::Parser;

    let mut extractor = Extractor::default();
    extractor.push_bytes(EXAMPLE_DATA);

    let mut parser = Parser::default();
    for frame in extractor.flatten() {
        parser.parse(&frame).expect("example data must parse");
    }

    assert_eq!(parser.fifo_depth_peaks(), [104, 104, 104, 0, 104]);
}

/// FBB never computes the 8-channel accumulator. The 6-channel one still gates on bit 3 of
/// `substream_info` (0b00001101 here, so it counts substreams 0 and 1), and the 16-channel
/// one needs a fourth substream FBB cannot have.
#[test]
fn fbb_skips_the_eightch_accumulator_rather_than_capping_it() {
    use extract::Extractor;
    use parse::Parser;

    let mut extractor = Extractor::default();
    extractor.push_bytes(EXAMPLE_DATA_FBB);

    let mut parser = Parser::default();
    for frame in extractor.flatten() {
        parser.parse(&frame).expect("FBB example data must parse");
    }

    let peaks = parser.fifo_depth_peaks();
    assert_eq!(peaks, [172, 482, 0, 0, 482]);
    assert_eq!(peaks[2], 0, "FBB must never accumulate an 8-channel depth");
}