Skip to main content

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

1use crate::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Default)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5pub struct Esds {
6    pub es_desc: EsDescriptor,
7}
8
9impl AtomExt for Esds {
10    type Ext = ();
11
12    const KIND_EXT: FourCC = FourCC::new(b"esds");
13
14    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: ()) -> Result<Self> {
15        let mut es_desc = None;
16
17        while let Some(desc) = Descriptor::decode_maybe(buf)? {
18            match desc {
19                Descriptor::EsDescriptor(desc) => es_desc = Some(desc),
20                Descriptor::Unknown(tag, _) => {
21                    tracing::warn!("unknown descriptor: {:02X}", tag)
22                }
23                _ => return Err(Error::UnexpectedDescriptor(desc.tag())),
24            }
25        }
26
27        Ok(Esds {
28            es_desc: es_desc.ok_or(Error::MissingDescriptor(EsDescriptor::TAG))?,
29        })
30    }
31
32    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<()> {
33        encode_descriptor(EsDescriptor::TAG, &self.es_desc, buf)
34    }
35}
36
37/// Decode a descriptor body of `size` bytes, tolerating trailing bytes the
38/// specific descriptor doesn't consume.
39///
40/// MPEG-4 (ISO/IEC 14496-1) descriptors are length-prefixed and forward-
41/// compatible: the size field is authoritative and a parser skips to the
42/// declared end, ignoring extension fields (or writer padding) it doesn't
43/// understand. Unlike [`Decode::decode_exact`] this does not `ShortRead` on the
44/// leftover — e.g. a 2-byte `SLConfigDescriptor` (`predefined` + a trailing
45/// byte) decodes instead of failing the whole `esds`. The `size` slice still
46/// bounds the read, so a descriptor can never run past its declared length.
47fn decode_descriptor_body<T: Decode, B: Buf>(buf: &mut B, size: usize) -> Result<T> {
48    if buf.remaining() < size {
49        return Err(Error::OutOfBounds);
50    }
51    let mut inner = buf.slice(size);
52    let res = T::decode(&mut inner)?;
53    buf.advance(size);
54    Ok(res)
55}
56
57/// Encode a typed descriptor — tag, base-128 length prefix, then the body —
58/// borrowing `body` rather than taking ownership, so a caller holding a `&T`
59/// need not clone it into a [`Descriptor`] just to serialize it.
60fn encode_descriptor<T: Encode, B: BufMut>(tag: u8, body: &T, buf: &mut B) -> Result<()> {
61    tag.encode(buf)?;
62
63    // TODO This is inefficient; we could compute the size upfront.
64    let mut tmp = Vec::new();
65    body.encode(&mut tmp)?;
66
67    // Base-128 length prefix, most-significant 7-bit group first (ISO/IEC
68    // 14496-1 §8.3.3, matching this crate's `size << 7 | byte` decoder), and
69    // always at least one byte so a zero-length body still carries its
70    // mandatory `0x00` size prefix.
71    let size = tmp.len() as u32;
72    let mut groups = 1;
73    let mut s = size >> 7;
74    while s > 0 {
75        groups += 1;
76        s >>= 7;
77    }
78    for i in (0..groups).rev() {
79        let mut b = ((size >> (7 * i)) & 0x7F) as u8;
80        if i > 0 {
81            b |= 0x80;
82        }
83        b.encode(buf)?;
84    }
85
86    tmp.encode(buf)
87}
88
89macro_rules! descriptors {
90    ($($name:ident,)*) => {
91        #[derive(Debug, Clone, PartialEq, Eq)]
92        pub enum Descriptor {
93            $(
94                $name($name),
95            )*
96            Unknown(u8, Vec<u8>),
97        }
98
99        impl Decode for Descriptor {
100            fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
101                let tag = u8::decode(buf)?;
102
103                // At most four 7-bit groups; a fourth with its continuation bit set is malformed.
104                let mut size: u32 = 0;
105                for i in 0..4 {
106                    let b = u8::decode(buf)?;
107                    size = (size << 7) | (b & 0x7F) as u32;
108                    if b & 0x80 == 0 {
109                        break;
110                    }
111                    if i == 3 {
112                        return Err(Error::InvalidSize);
113                    }
114                }
115
116                match tag {
117                    $(
118                        $name::TAG => Ok(decode_descriptor_body::<$name, _>(buf, size as _)?.into()),
119                    )*
120                    _ => Ok(Descriptor::Unknown(tag, Vec::decode_exact(buf, size as _)?)),
121                }
122            }
123        }
124
125        impl DecodeMaybe for Descriptor {
126            fn decode_maybe<B: Buf>(buf: &mut B) -> Result<Option<Self>> {
127                match buf.has_remaining() {
128                    true => Descriptor::decode(buf).map(Some),
129                    false => Ok(None),
130                }
131            }
132        }
133
134        impl Encode for Descriptor {
135            fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
136                match self {
137                    $(
138                        Descriptor::$name(t) => encode_descriptor($name::TAG, t, buf),
139                    )*
140                    Descriptor::Unknown(tag, data) => encode_descriptor(*tag, data, buf),
141                }
142            }
143        }
144
145        impl Descriptor {
146            pub const fn tag(&self) -> u8 {
147                match self {
148                    $(
149                        Descriptor::$name(_) => $name::TAG,
150                    )*
151                    Descriptor::Unknown(tag, _) => *tag,
152                }
153            }
154        }
155
156        $(
157            impl From<$name> for Descriptor {
158                fn from(desc: $name) -> Self {
159                    Descriptor::$name(desc)
160                }
161            }
162        )*
163    };
164}
165
166descriptors! {
167    EsDescriptor,
168    DecoderConfig,
169    DecoderSpecific,
170    SLConfig,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Default)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
175pub struct EsDescriptor {
176    pub es_id: u16,
177    /// The ES this one depends on (`streamDependenceFlag`).
178    pub depends_on_es_id: Option<u16>,
179    /// Where the stream is stored instead (`URL_Flag`), as UTF-8 bytes.
180    pub url: Option<Vec<u8>>,
181    /// The ES carrying this one's object clock reference (`OCRstreamFlag`).
182    pub ocr_es_id: Option<u16>,
183    /// The 5-bit `streamPriority`.
184    pub stream_priority: u8,
185
186    pub dec_config: DecoderConfig,
187    pub sl_config: SLConfig,
188}
189
190impl EsDescriptor {
191    pub const TAG: u8 = 0x03;
192}
193
194impl Decode for EsDescriptor {
195    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
196        let es_id = u16::decode(buf)?;
197
198        // The flags announce optional fields that precede the child descriptors.
199        let flags = u8::decode(buf)?;
200        let depends_on_es_id = match flags & 0x80 {
201            0 => None,
202            _ => Some(u16::decode(buf)?),
203        };
204        let url = match flags & 0x40 {
205            0 => None,
206            _ => {
207                let len = u8::decode(buf)?;
208                Some(Vec::decode_exact(buf, len as usize)?)
209            }
210        };
211        let ocr_es_id = match flags & 0x20 {
212            0 => None,
213            _ => Some(u16::decode(buf)?),
214        };
215
216        let mut dec_config = None;
217        let mut sl_config = None;
218
219        while let Some(desc) = Descriptor::decode_maybe(buf)? {
220            match desc {
221                Descriptor::DecoderConfig(desc) => dec_config = Some(desc),
222                Descriptor::SLConfig(desc) => sl_config = Some(desc),
223                Descriptor::Unknown(tag, _) => tracing::warn!("unknown descriptor: {:02X}", tag),
224                desc => return Err(Error::UnexpectedDescriptor(desc.tag())),
225            }
226        }
227
228        Ok(EsDescriptor {
229            es_id,
230            depends_on_es_id,
231            url,
232            ocr_es_id,
233            stream_priority: flags & 0x1F,
234            dec_config: dec_config.ok_or(Error::MissingDescriptor(DecoderConfig::TAG))?,
235            sl_config: sl_config.ok_or(Error::MissingDescriptor(SLConfig::TAG))?,
236        })
237    }
238}
239
240impl Encode for EsDescriptor {
241    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
242        if self.stream_priority > 0x1F {
243            return Err(Error::InvalidCombination(
244                "ES descriptor stream priority exceeds 5 bits",
245            ));
246        }
247        let url_len =
248            match &self.url {
249                Some(url) => Some(u8::try_from(url.len()).map_err(|_| {
250                    Error::InvalidCombination("ES descriptor URL exceeds 255 bytes")
251                })?),
252                None => None,
253            };
254
255        self.es_id.encode(buf)?;
256
257        let mut flags = self.stream_priority;
258        if self.depends_on_es_id.is_some() {
259            flags |= 0x80;
260        }
261        if self.url.is_some() {
262            flags |= 0x40;
263        }
264        if self.ocr_es_id.is_some() {
265            flags |= 0x20;
266        }
267        flags.encode(buf)?;
268
269        self.depends_on_es_id.encode(buf)?;
270        if let (Some(len), Some(url)) = (url_len, &self.url) {
271            len.encode(buf)?;
272            url.encode(buf)?;
273        }
274        self.ocr_es_id.encode(buf)?;
275
276        encode_descriptor(DecoderConfig::TAG, &self.dec_config, buf)?;
277        encode_descriptor(SLConfig::TAG, &self.sl_config, buf)?;
278
279        Ok(())
280    }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Default)]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285pub struct DecoderConfig {
286    pub object_type_indication: u8,
287    pub stream_type: u8,
288    pub up_stream: u8,
289    pub buffer_size_db: u24,
290    pub max_bitrate: u32,
291    pub avg_bitrate: u32,
292    /// The `DecoderSpecificInfo` (tag 0x05). ISO/IEC 14496-1 makes this
293    /// child optional ("if available"): a stream whose config can be derived
294    /// in-band (e.g. AAC carried with ADTS headers) legitimately omits it, so a
295    /// missing tag 5 must not fail the whole `esds`. `None` means the container
296    /// carried no out-of-band config.
297    pub dec_specific: Option<DecoderSpecific>,
298}
299
300impl DecoderConfig {
301    pub const TAG: u8 = 0x04;
302}
303
304impl Decode for DecoderConfig {
305    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
306        let object_type_indication = u8::decode(buf)?;
307        let byte_a = u8::decode(buf)?;
308        let stream_type = (byte_a & 0xFC) >> 2;
309        let up_stream = byte_a & 0x02;
310        let buffer_size_db = u24::decode(buf)?;
311        let max_bitrate = u32::decode(buf)?;
312        let avg_bitrate = u32::decode(buf)?;
313
314        let mut dec_specific = None;
315
316        while let Some(desc) = Descriptor::decode_maybe(buf)? {
317            match desc {
318                Descriptor::DecoderSpecific(desc) => dec_specific = Some(desc),
319                Descriptor::Unknown(tag, _) => tracing::warn!("unknown descriptor: {:02X}", tag),
320                desc => return Err(Error::UnexpectedDescriptor(desc.tag())),
321            }
322        }
323
324        Ok(DecoderConfig {
325            object_type_indication,
326            stream_type,
327            up_stream,
328            buffer_size_db,
329            max_bitrate,
330            avg_bitrate,
331            dec_specific,
332        })
333    }
334}
335
336impl Encode for DecoderConfig {
337    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
338        self.object_type_indication.encode(buf)?;
339        ((self.stream_type << 2) + (self.up_stream & 0x02) + 1).encode(buf)?; // 1 reserved
340        self.buffer_size_db.encode(buf)?;
341        self.max_bitrate.encode(buf)?;
342        self.avg_bitrate.encode(buf)?;
343
344        if let Some(dec_specific) = &self.dec_specific {
345            encode_descriptor(DecoderSpecific::TAG, dec_specific, buf)?;
346        }
347
348        Ok(())
349    }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Default)]
353#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
354pub struct DecoderSpecific {
355    pub profile: u8,
356    pub freq_index: u8,
357    pub chan_conf: u8,
358    /// The complete `DecoderSpecificInfo` payload. For AAC this is the
359    /// AudioSpecificConfig — the `profile` / `freq_index` / `chan_conf` fields
360    /// above are a parsed view of its prefix. For a non-AAC object type (e.g.
361    /// the MPEG-4 Visual VOS/VOL config carried by an `mp4v` `esds`) the fields
362    /// are not meaningful, but the bytes are preserved verbatim so the
363    /// descriptor round-trips faithfully. Empty on a hand-constructed AAC
364    /// config, in which case [`Encode`] re-derives the 2-byte AudioSpecificConfig
365    /// from the fields.
366    pub raw: Vec<u8>,
367}
368
369impl DecoderSpecific {
370    pub const TAG: u8 = 0x05;
371}
372
373/// Best-effort parse of the AAC AudioSpecificConfig prefix (`audioObjectType`,
374/// `samplingFrequencyIndex`, `channelConfiguration`) from the raw payload.
375/// Returns zeros when the payload is too short to parse (e.g. a non-AAC config);
376/// the raw bytes remain the source of truth for round-tripping.
377fn parse_audio_specific_config(raw: &[u8]) -> (u8, u8, u8) {
378    if raw.len() < 2 {
379        return (0, 0, 0);
380    }
381    let byte_a = raw[0];
382    let byte_b = raw[1];
383
384    let mut profile = byte_a >> 3;
385    if profile == 31 {
386        // Escape: 6-bit audioObjectTypeExt = byte_a[2:0] << 3 | byte_b[7:5].
387        profile = 32 + (((byte_a & 7) << 3) | (byte_b >> 5));
388    }
389
390    let freq_index = if profile > 31 {
391        (byte_b >> 1) & 0x0F
392    } else {
393        ((byte_a & 0x07) << 1) + (byte_b >> 7)
394    };
395
396    let chan_conf = if freq_index == 15 {
397        // The 24-bit explicit sample rate precedes the channel config.
398        if raw.len() >= 5 {
399            let sample_rate =
400                (u32::from(raw[2]) << 16) | (u32::from(raw[3]) << 8) | u32::from(raw[4]);
401            ((sample_rate >> 4) & 0x0F) as u8
402        } else {
403            0
404        }
405    } else if profile > 31 {
406        if raw.len() >= 3 {
407            // 4-bit channelConfiguration = byte_b[0] << 3 | raw[2][7:5].
408            ((byte_b & 1) << 3) | (raw[2] >> 5)
409        } else {
410            0
411        }
412    } else {
413        (byte_b >> 3) & 0x0F
414    };
415
416    (profile, freq_index, chan_conf)
417}
418
419impl Decode for DecoderSpecific {
420    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
421        // Capture the complete payload (the descriptor body is already
422        // size-bounded by `decode_descriptor_body`) so any object type
423        // round-trips; the AAC fields are a best-effort parse of its prefix.
424        let raw = Vec::decode(buf)?;
425        let (profile, freq_index, chan_conf) = parse_audio_specific_config(&raw);
426
427        Ok(DecoderSpecific {
428            profile,
429            freq_index,
430            chan_conf,
431            raw,
432        })
433    }
434}
435
436impl Encode for DecoderSpecific {
437    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
438        if self.raw.is_empty() {
439            // Hand-constructed AAC config with no preserved bytes: re-derive the
440            // 2-byte AudioSpecificConfig from the fields.
441            ((self.profile << 3) + (self.freq_index >> 1)).encode(buf)?;
442            ((self.freq_index << 7) + (self.chan_conf << 3)).encode(buf)?;
443        } else {
444            // Emit the preserved payload verbatim (faithful for any object type).
445            self.raw.encode(buf)?;
446        }
447
448        Ok(())
449    }
450}
451
452#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
453#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
454pub struct SLConfig {}
455
456impl SLConfig {
457    pub const TAG: u8 = 0x06;
458}
459
460impl Decode for SLConfig {
461    fn decode<B: Buf>(buf: &mut B) -> Result<Self> {
462        u8::decode(buf)?; // pre-defined
463        Ok(SLConfig {})
464    }
465}
466
467impl Encode for SLConfig {
468    fn encode<B: BufMut>(&self, buf: &mut B) -> Result<()> {
469        2u8.encode(buf)?; // pre-defined
470        Ok(())
471    }
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477
478    fn es_descriptor() -> EsDescriptor {
479        EsDescriptor {
480            es_id: 0x1234,
481            depends_on_es_id: Some(0x5678),
482            url: Some(b"https://example.com/audio".to_vec()),
483            ocr_es_id: Some(0x9ABC),
484            stream_priority: 17,
485            dec_config: DecoderConfig::default(),
486            sl_config: SLConfig::default(),
487        }
488    }
489
490    // The optional fields sit between the flags and the child descriptors, so
491    // ignoring them used to misread their bytes as a descriptor.
492    #[test]
493    fn test_es_descriptor_optional_fields_roundtrip() {
494        let mut flagged = es_descriptor();
495        for _ in 0..2 {
496            let mut buf = Vec::new();
497            Descriptor::from(flagged.clone()).encode(&mut buf).unwrap();
498            assert_eq!(
499                Descriptor::decode(&mut buf.as_slice()).unwrap(),
500                Descriptor::from(flagged.clone())
501            );
502
503            // Then without any optional field: a flags byte of just the priority.
504            flagged.depends_on_es_id = None;
505            flagged.url = None;
506            flagged.ocr_es_id = None;
507        }
508    }
509
510    #[test]
511    fn test_es_descriptor_rejects_unencodable_fields() {
512        let mut desc = es_descriptor();
513        desc.stream_priority = 0x20;
514        assert!(desc.encode(&mut Vec::new()).is_err());
515
516        let mut desc = es_descriptor();
517        desc.url = Some(vec![b'a'; 256]);
518        assert!(desc.encode(&mut Vec::new()).is_err());
519    }
520
521    #[test]
522    fn test_descriptor_size_rejects_fifth_byte() {
523        let buf = [0x7F, 0x80, 0x80, 0x80, 0x80, 0x00];
524        assert!(matches!(
525            Descriptor::decode(&mut buf.as_slice()),
526            Err(Error::InvalidSize)
527        ));
528    }
529
530    // An `esds` whose `SLConfigDescriptor` (tag 0x06) declares 2 bytes — a
531    // `predefined` value plus a trailing byte — instead of the usual 1.
532    // `SLConfig::decode` reads only `predefined`, so the strict `decode_exact`
533    // used to `ShortRead` and fail the whole `esds` with `UnderDecode(esds)`.
534    // ISO 14496-1 makes the descriptor length authoritative, so the trailing
535    // byte must be skipped. Descriptor tree: EsDescriptor(0x03) → DecoderConfig
536    // (0x04) → DecoderSpecific(0x05, AAC-LC 48 kHz) + SLConfig(0x06, 2 bytes).
537    const ESDS_2BYTE_SLCONFIG: &[u8] = &[
538        0x00, 0x00, 0x00, 0x28, b'e', b's', b'd', b's', // esds box, size 40
539        0x00, 0x00, 0x00, 0x00, // version + flags
540        0x03, 0x1a, 0x00, 0x01, 0x00, // EsDescriptor: size 26, es_id 1, flags 0
541        0x04, 0x11, 0x40, 0x15, // DecoderConfig: size 17, oti 0x40, stream/up 0x15
542        0x00, 0x00, 0x00, // buffer_size_db
543        0x00, 0x00, 0x00, 0x00, // max_bitrate
544        0x00, 0x00, 0x00, 0x00, // avg_bitrate
545        0x05, 0x02, 0x11, 0x90, // DecoderSpecific: size 2 (profile 2, freq 3, chan 2)
546        0x06, 0x02, 0x02, 0x15, // SLConfig: size 2 = predefined 0x02 + a trailing byte
547    ];
548
549    #[test]
550    fn test_esds_two_byte_slconfig() {
551        let esds =
552            Esds::decode(&mut &ESDS_2BYTE_SLCONFIG[..]).expect("2-byte SLConfig must be tolerated");
553        let dec = esds
554            .es_desc
555            .dec_config
556            .dec_specific
557            .expect("DecoderSpecificInfo present");
558        assert_eq!(dec.profile, 2, "AAC-LC");
559        assert_eq!(dec.freq_index, 3, "48 kHz");
560    }
561
562    // A `DecoderConfigDescriptor` (tag 0x04) with NO `DecoderSpecificInfo`
563    // (tag 0x05) child — valid per ISO/IEC 14496-1, where the DecSpecificInfo
564    // is optional (e.g. AAC whose config is carried in-band via ADTS headers).
565    // Before the fix the mandatory `MissingDescriptor(0x05)` rejected the whole
566    // `esds`; now the field decodes to `None`. Descriptor tree: EsDescriptor
567    // (0x03) → DecoderConfig(0x04, no tag-5 child) + SLConfig(0x06).
568    const ESDS_NO_DEC_SPECIFIC: &[u8] = &[
569        0x00, 0x00, 0x00, 0x23, b'e', b's', b'd', b's', // esds box, size 35
570        0x00, 0x00, 0x00, 0x00, // version + flags
571        0x03, 0x15, 0x00, 0x01, 0x00, // EsDescriptor: size 21, es_id 1, flags 0
572        0x04, 0x0d, 0x40, 0x15, // DecoderConfig: size 13, oti 0x40 (AAC), stream/up 0x15
573        0x00, 0x00, 0x00, // buffer_size_db
574        0x00, 0x00, 0x00, 0x00, // max_bitrate
575        0x00, 0x00, 0x00, 0x00, // avg_bitrate
576        0x06, 0x01, 0x02, // SLConfig: size 1, predefined 0x02
577    ];
578
579    #[test]
580    fn test_esds_missing_dec_specific() {
581        let esds = Esds::decode(&mut &ESDS_NO_DEC_SPECIFIC[..])
582            .expect("a DecoderConfig without a DecoderSpecificInfo must be tolerated");
583        assert_eq!(esds.es_desc.dec_config.object_type_indication, 0x40);
584        assert!(
585            esds.es_desc.dec_config.dec_specific.is_none(),
586            "no tag-5 child decodes to None, not an error"
587        );
588
589        // And it round-trips: a `None` dec_specific emits no tag-5 descriptor.
590        let mut buf = Vec::new();
591        esds.encode(&mut buf).unwrap();
592        let again = Esds::decode(&mut buf.as_slice()).unwrap();
593        assert_eq!(again, esds);
594    }
595
596    // A zero-length descriptor must still carry its mandatory 1-byte length
597    // prefix (`[tag, 0x00]`). Emitting just the tag would let the following
598    // sibling's first byte be misread as this descriptor's length, corrupting
599    // the rest of the tree.
600    #[test]
601    fn test_unknown_descriptor_empty_body_roundtrips() {
602        let mut buf = Vec::new();
603        Descriptor::Unknown(0x7F, Vec::new())
604            .encode(&mut buf)
605            .unwrap();
606        // A second descriptor: if the empty body dropped its length byte, this
607        // one would be swallowed and fail to decode.
608        Descriptor::from(SLConfig {}).encode(&mut buf).unwrap();
609
610        assert_eq!(&buf[..2], &[0x7F, 0x00], "empty body still emits a length");
611
612        let mut cur = buf.as_slice();
613        assert_eq!(
614            Descriptor::decode(&mut cur).unwrap(),
615            Descriptor::Unknown(0x7F, Vec::new())
616        );
617        assert_eq!(
618            Descriptor::decode(&mut cur).unwrap().tag(),
619            SLConfig::TAG,
620            "the following descriptor is intact"
621        );
622    }
623
624    // A body >= 128 bytes needs a multi-byte base-128 length. It must be emitted
625    // most-significant group first, so the decoder (which accumulates
626    // `size << 7 | byte`) reads it back unchanged. 200 => `[0x81, 0x48]`.
627    #[test]
628    fn test_descriptor_multibyte_length_roundtrips() {
629        let body = vec![0xABu8; 200];
630        let mut buf = Vec::new();
631        Descriptor::Unknown(0x7F, body.clone())
632            .encode(&mut buf)
633            .unwrap();
634        assert_eq!(
635            &buf[..3],
636            &[0x7F, 0x81, 0x48],
637            "length 200, MSB group first"
638        );
639
640        let mut cur = buf.as_slice();
641        assert_eq!(
642            Descriptor::decode(&mut cur).unwrap(),
643            Descriptor::Unknown(0x7F, body)
644        );
645    }
646
647    // AudioSpecificConfig with the 5-bit audioObjectType == 31 escape. The real
648    // AOT is `32 + audioObjectTypeExt`, a 6-bit field split as byte_a[2:0]<<3 |
649    // byte_b[7:5]; the 4-bit channelConfiguration is byte_b[0]<<3 | raw[2][7:5].
650    // These bytes encode AOT 42, samplingFrequencyIndex 4, channelConfig 9.
651    #[test]
652    fn test_asc_escape_bit_packing() {
653        let (profile, freq_index, chan_conf) = parse_audio_specific_config(&[0xF9, 0x49, 0x20]);
654        assert_eq!(profile, 42, "32 + ((0xF9 & 7) << 3 | 0x49 >> 5)");
655        assert_eq!(freq_index, 4, "(0x49 >> 1) & 0x0F");
656        assert_eq!(chan_conf, 9, "((0x49 & 1) << 3) | (0x20 >> 5)");
657    }
658}