scuffle_flv/audio/body/
enhanced.rs

1//! Enhanced audio tag body
2//!
3//! Types and functions defined by the enhanced RTMP spec, page 19, ExAudioTagBody.
4
5use std::io::{self, Read};
6
7use byteorder::{BigEndian, ReadBytesExt};
8use bytes::{Buf, Bytes};
9use nutype_enum::nutype_enum;
10use scuffle_bytes_util::BytesCursorExt;
11
12use crate::audio::header::enhanced::{AudioFourCc, AudioPacketType, ExAudioTagHeader, ExAudioTagHeaderContent};
13
14nutype_enum! {
15    /// Audio channel order
16    ///
17    /// Defined by:
18    /// - Enhanced RTMP spec, page 22-23, ExAudioTagBody
19    pub enum AudioChannelOrder(u8) {
20        /// Only the channel count is specified, without any further information about the channel order.
21        Unspecified = 0,
22        /// The native channel order (i.e., the channels are in the same order in which as defined in the [`AudioChannel`] enum).
23        Native = 1,
24        /// The channel order does not correspond to any predefined order and is stored as an explicit map.
25        Custom = 2,
26    }
27}
28
29nutype_enum! {
30    /// Channel mappings enum
31    ///
32    /// See <https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels> and
33    /// <https://en.wikipedia.org/wiki/22.2_surround_sound> for more information.
34    pub enum AudioChannel(u8) {
35        // Commonly used speaker configurations:
36
37        /// Front left
38        FrontLeft = 0,
39        /// Front right
40        FrontRight = 1,
41        /// Front center
42        FrontCenter = 2,
43        /// Low frequency
44        LowFrequency1 = 3,
45        /// Back left
46        BackLeft = 4,
47        /// Back right
48        BackRight = 5,
49        /// Front left of center
50        FrontLeftCenter = 6,
51        /// Front right of center
52        FrontRightCenter = 7,
53        /// Back center
54        BackCenter = 8,
55        /// Side left
56        SideLeft = 9,
57        /// Side right
58        SideRight = 10,
59        /// Top center
60        TopCenter = 11,
61        /// Front left height
62        TopFrontLeft = 12,
63        /// Front center height
64        TopFrontCenter = 13,
65        /// Front right height
66        TopFrontRight = 14,
67        /// Rear left height
68        TopBackLeft = 15,
69        /// Rear center height
70        TopBackCenter = 16,
71        /// Rear right height
72        TopBackRight = 17,
73
74        // Mappings to complete 22.2 multichannel audio, as standardized in SMPTE ST2036-2-2008:
75
76        /// Low frequency 2
77        LowFrequency2 = 18,
78        /// Top side left
79        TopSideLeft = 19,
80        /// Top side right
81        TopSideRight = 20,
82        /// Bottom front center
83        BottomFrontCenter = 21,
84        /// Bottom front left
85        BottomFrontLeft = 22,
86        /// Bottom front right
87        BottomFrontRight = 23,
88        /// Channel is empty and can be safely skipped.
89        Unused = 0xfe,
90        /// Channel contains data, but its speaker configuration is unknown.
91        Unknown = 0xff,
92    }
93}
94
95/// Mask used to indicate which channels are present in the stream.
96///
97/// See <https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels> and
98/// <https://en.wikipedia.org/wiki/22.2_surround_sound> for more information.
99#[bitmask_enum::bitmask(u32)]
100pub enum AudioChannelMask {
101    // Masks for commonly used speaker configurations:
102    /// Front left
103    FrontLeft = 0x000001,
104    /// Front right
105    FrontRight = 0x000002,
106    /// Front center
107    FrontCenter = 0x000004,
108    /// Low frequency
109    LowFrequency1 = 0x000008,
110    /// Back left
111    BackLeft = 0x000010,
112    /// Back right
113    BackRight = 0x000020,
114    /// Front left of center
115    FrontLeftCenter = 0x000040,
116    /// Front right of center
117    FrontRightCenter = 0x000080,
118    /// Back center
119    BackCenter = 0x000100,
120    /// Side left
121    SideLeft = 0x000200,
122    /// Side right
123    SideRight = 0x000400,
124    /// Top center
125    TopCenter = 0x000800,
126    /// Front left height
127    TopFrontLeft = 0x001000,
128    /// Front center height
129    TopFrontCenter = 0x002000,
130    /// Front right height
131    TopFrontRight = 0x004000,
132    /// Rear left height
133    TopBackLeft = 0x008000,
134    /// Rear center height
135    TopBackCenter = 0x010000,
136    /// Rear right height
137    TopBackRight = 0x020000,
138
139    // Completes 22.2 multichannel audio, as
140    // standardized in SMPTE ST2036-2-2008:
141    /// Low frequency 2
142    LowFrequency2 = 0x040000,
143    /// Top side left
144    TopSideLeft = 0x080000,
145    /// Top side right
146    TopSideRight = 0x100000,
147    /// Bottom front center
148    BottomFrontCenter = 0x200000,
149    /// Bottom front left
150    BottomFrontLeft = 0x400000,
151    /// Bottom front right
152    BottomFrontRight = 0x800000,
153}
154
155/// Multichannel configuration
156///
157/// Describes the configuration of the audio channels in a multichannel audio stream.
158///
159/// Contained in an [`AudioPacket::MultichannelConfig`].
160#[derive(Debug, Clone, PartialEq)]
161pub enum MultichannelConfigOrder {
162    /// Custom channel order
163    ///
164    /// The channels have a custom order that is explicitly defined by this packet.
165    Custom(Vec<AudioChannel>),
166    /// Native channel order
167    ///
168    /// Only the channels flagged in this packet are present in the stream
169    /// in the order they are defined by the [`AudioChannelMask`].
170    ///
171    /// > You can perform a Bitwise AND
172    /// > (i.e., audioChannelFlags & AudioChannelMask.xxx) to see if a
173    /// > specific audio channel is present.
174    Native(AudioChannelMask),
175    /// The channel order is unspecified, only the channel count is known.
176    Unspecified,
177    /// An unknown channel order.
178    ///
179    /// Neither [`Unspecified`](AudioChannelOrder::Unspecified), [`Native`](AudioChannelOrder::Native),
180    /// nor [`Custom`](AudioChannelOrder::Custom).
181    Unknown(AudioChannelOrder),
182}
183
184/// Audio packet
185///
186/// Appears as part of the [`ExAudioTagBody`].
187///
188/// Defined by:
189/// - Enhanced RTMP spec, page 23-25, ExAudioTagBody
190#[derive(Debug, Clone, PartialEq)]
191pub enum AudioPacket {
192    /// Multichannel configuration
193    ///
194    /// > Specify a speaker for a channel as it appears in the bitstream.
195    /// > This is needed if the codec is not self-describing for channel mapping.
196    MultichannelConfig {
197        /// The number of channels in the audio stream.
198        channel_count: u8,
199        /// The multichannel configuration.
200        ///
201        /// Specifies the order of the channels in the audio stream.
202        multichannel_config: MultichannelConfigOrder,
203    },
204    /// Indicates the end of a sequence of audio packets.
205    SequenceEnd,
206    /// Indicates the start of a sequence of audio packets.
207    SequenceStart {
208        /// The header data for the sequence.
209        header_data: Bytes,
210    },
211    /// Coded audio frames.
212    CodedFrames {
213        /// The audio data.
214        data: Bytes,
215    },
216    /// An unknown [`AudioPacketType`].
217    Unknown {
218        /// The unknown packet type.
219        audio_packet_type: AudioPacketType,
220        /// The data.
221        data: Bytes,
222    },
223}
224
225impl AudioPacket {
226    /// Demux an [`AudioPacket`] from the given reader.
227    ///
228    /// This is implemented as per spec, Enhanced RTMP page 23-25, ExAudioTagBody.
229    pub fn demux(header: &ExAudioTagHeader, reader: &mut io::Cursor<Bytes>) -> io::Result<Self> {
230        let has_multiple_tracks = !matches!(
231            header.content,
232            ExAudioTagHeaderContent::NoMultiTrack(_) | ExAudioTagHeaderContent::OneTrack(_)
233        );
234
235        let size_of_audio_track = if has_multiple_tracks {
236            Some(reader.read_u24::<BigEndian>()? as usize)
237        } else {
238            None
239        };
240
241        match header.audio_packet_type {
242            AudioPacketType::MultichannelConfig => {
243                let audio_channel_order = AudioChannelOrder::from(reader.read_u8()?);
244                let channel_count = reader.read_u8()?;
245
246                let multichannel_config = match audio_channel_order {
247                    AudioChannelOrder::Custom => {
248                        let channels = reader.extract_bytes(channel_count as usize)?;
249
250                        MultichannelConfigOrder::Custom(channels.into_iter().map(AudioChannel::from).collect())
251                    }
252                    AudioChannelOrder::Native => {
253                        let audio_channel_flags = AudioChannelMask::from(reader.read_u32::<BigEndian>()?);
254
255                        MultichannelConfigOrder::Native(audio_channel_flags)
256                    }
257                    AudioChannelOrder::Unspecified => MultichannelConfigOrder::Unspecified,
258                    _ => MultichannelConfigOrder::Unknown(audio_channel_order),
259                };
260
261                Ok(Self::MultichannelConfig {
262                    channel_count,
263                    multichannel_config,
264                })
265            }
266            AudioPacketType::SequenceEnd => Ok(Self::SequenceEnd),
267            AudioPacketType::SequenceStart => {
268                let header_data = reader.extract_bytes(size_of_audio_track.unwrap_or(reader.remaining()))?;
269
270                Ok(Self::SequenceStart { header_data })
271            }
272            AudioPacketType::CodedFrames => {
273                let data = reader.extract_bytes(size_of_audio_track.unwrap_or(reader.remaining()))?;
274
275                Ok(Self::CodedFrames { data })
276            }
277            _ => {
278                let data = reader.extract_bytes(size_of_audio_track.unwrap_or(reader.remaining()))?;
279
280                Ok(Self::Unknown {
281                    audio_packet_type: header.audio_packet_type,
282                    data,
283                })
284            }
285        }
286    }
287}
288
289/// One audio track contained in a multitrack audio.
290#[derive(Debug, Clone, PartialEq)]
291pub struct AudioTrack {
292    /// The audio FOURCC of this track.
293    pub audio_four_cc: AudioFourCc,
294    /// The audio track ID.
295    ///
296    /// > For identifying the highest priority (a.k.a., default track)
297    /// > or highest quality track, it is RECOMMENDED to use trackId
298    /// > set to zero. For tracks of lesser priority or quality, use
299    /// > multiple instances of trackId with ascending numerical values.
300    /// > The concept of priority or quality can have multiple
301    /// > interpretations, including but not limited to bitrate,
302    /// > resolution, default angle, and language. This recommendation
303    /// > serves as a guideline intended to standardize track numbering
304    /// > across various applications.
305    pub audio_track_id: u8,
306    /// The audio packet contained in this track.
307    pub packet: AudioPacket,
308}
309
310/// `ExAudioTagBody`
311///
312/// Defined by:
313/// - Enhanced RTMP spec, page 22-25, ExAudioTagBody
314#[derive(Debug, Clone, PartialEq)]
315pub enum ExAudioTagBody {
316    /// The body is not a multitrack body.
317    NoMultitrack {
318        /// The audio FOURCC of this body.
319        audio_four_cc: AudioFourCc,
320        /// The audio packet contained in this body.
321        packet: AudioPacket,
322    },
323    /// The body is a multitrack body.
324    ///
325    /// This variant contains multiple audio tracks.
326    /// See [`AudioTrack`] for more information.
327    ManyTracks(Vec<AudioTrack>),
328}
329
330impl ExAudioTagBody {
331    /// Demux an [`ExAudioTagBody`] from the given reader.
332    ///
333    /// This is implemented as per Enhanced RTMP spec, page 22-25, ExAudioTagBody.
334    pub fn demux(header: &ExAudioTagHeader, reader: &mut io::Cursor<Bytes>) -> io::Result<Self> {
335        let mut tracks = Vec::new();
336
337        loop {
338            let audio_four_cc = match header.content {
339                ExAudioTagHeaderContent::ManyTracksManyCodecs => {
340                    let mut audio_four_cc = [0; 4];
341                    reader.read_exact(&mut audio_four_cc)?;
342                    AudioFourCc::from(audio_four_cc)
343                }
344                ExAudioTagHeaderContent::OneTrack(audio_four_cc) => audio_four_cc,
345                ExAudioTagHeaderContent::ManyTracks(audio_four_cc) => audio_four_cc,
346                ExAudioTagHeaderContent::NoMultiTrack(audio_four_cc) => audio_four_cc,
347                ExAudioTagHeaderContent::Unknown { audio_four_cc, .. } => audio_four_cc,
348            };
349
350            // if isAudioMultitrack
351            let audio_track_id = if !matches!(header.content, ExAudioTagHeaderContent::NoMultiTrack(_)) {
352                Some(reader.read_u8()?)
353            } else {
354                None
355            };
356
357            let packet = AudioPacket::demux(header, reader)?;
358
359            if let Some(audio_track_id) = audio_track_id {
360                // audio_track_id is only set if this is a multitrack audio, in other words, if `isAudioMultitrack` is true
361                tracks.push(AudioTrack {
362                    audio_four_cc,
363                    audio_track_id,
364                    packet,
365                });
366
367                // the loop only continues if there is still data to read and this is a audio with multiple tracks
368                if !matches!(header.content, ExAudioTagHeaderContent::OneTrack(_)) && reader.has_remaining() {
369                    continue;
370                }
371
372                break;
373            } else {
374                // exit early if this is a single track audio only completing one loop iteration
375                return Ok(Self::NoMultitrack { audio_four_cc, packet });
376            }
377        }
378
379        // at this point we know this is a multitrack audio because a single track audio would have exited early
380        Ok(Self::ManyTracks(tracks))
381    }
382}
383
384#[cfg(test)]
385#[cfg_attr(all(test, coverage_nightly), coverage(off))]
386mod tests {
387    use bytes::Bytes;
388
389    use super::AudioPacket;
390    use crate::audio::body::enhanced::{
391        AudioChannel, AudioChannelMask, AudioChannelOrder, AudioTrack, ExAudioTagBody, MultichannelConfigOrder,
392    };
393    use crate::audio::header::enhanced::{AudioFourCc, AudioPacketType, ExAudioTagHeader, ExAudioTagHeaderContent};
394    use crate::common::AvMultitrackType;
395
396    #[test]
397    fn simple_audio_packets_demux() {
398        let data = &[42, 42, 42, 42];
399
400        let packet = AudioPacket::demux(
401            &ExAudioTagHeader {
402                audio_packet_mod_exs: vec![],
403                audio_packet_type: AudioPacketType::SequenceStart,
404                content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
405            },
406            &mut std::io::Cursor::new(Bytes::from_static(data)),
407        )
408        .unwrap();
409        assert_eq!(
410            packet,
411            AudioPacket::SequenceStart {
412                header_data: Bytes::from_static(data)
413            }
414        );
415
416        let packet = AudioPacket::demux(
417            &ExAudioTagHeader {
418                audio_packet_mod_exs: vec![],
419                audio_packet_type: AudioPacketType::CodedFrames,
420                content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
421            },
422            &mut std::io::Cursor::new(Bytes::from_static(data)),
423        )
424        .unwrap();
425        assert_eq!(
426            packet,
427            AudioPacket::CodedFrames {
428                data: Bytes::from_static(data)
429            }
430        );
431
432        let packet = AudioPacket::demux(
433            &ExAudioTagHeader {
434                audio_packet_mod_exs: vec![],
435                audio_packet_type: AudioPacketType::SequenceEnd,
436                content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
437            },
438            &mut std::io::Cursor::new(Bytes::from_static(data)),
439        )
440        .unwrap();
441        assert_eq!(packet, AudioPacket::SequenceEnd,);
442
443        let packet = AudioPacket::demux(
444            &ExAudioTagHeader {
445                audio_packet_mod_exs: vec![],
446                audio_packet_type: AudioPacketType(8),
447                content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
448            },
449            &mut std::io::Cursor::new(Bytes::from_static(data)),
450        )
451        .unwrap();
452        assert_eq!(
453            packet,
454            AudioPacket::Unknown {
455                audio_packet_type: AudioPacketType(8),
456                data: Bytes::from_static(data),
457            },
458        );
459    }
460
461    #[test]
462    fn audio_packet_with_size_demux() {
463        let data = &[
464            0, 0, 2, // size
465            42, 42, // data
466            13, 37, // should be ignored
467        ];
468
469        let header = ExAudioTagHeader {
470            audio_packet_mod_exs: vec![],
471            audio_packet_type: AudioPacketType::CodedFrames,
472            content: ExAudioTagHeaderContent::ManyTracks(AudioFourCc::Aac),
473        };
474
475        let packet = AudioPacket::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
476
477        assert_eq!(
478            packet,
479            AudioPacket::CodedFrames {
480                data: Bytes::from_static(&[42, 42])
481            },
482        );
483    }
484
485    #[test]
486    fn audio_packet_custom_multichannel_config_demux() {
487        let data = &[
488            2, // channel order custom
489            2, // channel count
490            0, 1, // channels
491        ];
492
493        let header = ExAudioTagHeader {
494            audio_packet_mod_exs: vec![],
495            audio_packet_type: AudioPacketType::MultichannelConfig,
496            content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
497        };
498
499        let packet = AudioPacket::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
500
501        assert_eq!(
502            packet,
503            AudioPacket::MultichannelConfig {
504                channel_count: 2,
505                multichannel_config: MultichannelConfigOrder::Custom(vec![
506                    AudioChannel::FrontLeft,
507                    AudioChannel::FrontRight
508                ])
509            },
510        );
511    }
512
513    #[test]
514    fn audio_packet_native_multichannel_config_demux() {
515        let data = &[
516            1, // channel order native
517            2, // channel count
518            0, 0, 0, 3, // channels
519        ];
520
521        let header = ExAudioTagHeader {
522            audio_packet_mod_exs: vec![],
523            audio_packet_type: AudioPacketType::MultichannelConfig,
524            content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
525        };
526
527        let packet = AudioPacket::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
528
529        assert_eq!(
530            packet,
531            AudioPacket::MultichannelConfig {
532                channel_count: 2,
533                multichannel_config: MultichannelConfigOrder::Native(
534                    AudioChannelMask::FrontLeft | AudioChannelMask::FrontRight
535                )
536            },
537        );
538    }
539
540    #[test]
541    fn audio_packet_other_multichannel_config_demux() {
542        let data = &[
543            0, // channel order unspecified
544            2, // channel count
545        ];
546
547        let header = ExAudioTagHeader {
548            audio_packet_mod_exs: vec![],
549            audio_packet_type: AudioPacketType::MultichannelConfig,
550            content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
551        };
552
553        let packet = AudioPacket::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
554
555        assert_eq!(
556            packet,
557            AudioPacket::MultichannelConfig {
558                channel_count: 2,
559                multichannel_config: MultichannelConfigOrder::Unspecified,
560            },
561        );
562
563        let data = &[
564            4, // channel order unknown
565            2, // channel count
566        ];
567
568        let header = ExAudioTagHeader {
569            audio_packet_mod_exs: vec![],
570            audio_packet_type: AudioPacketType::MultichannelConfig,
571            content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
572        };
573
574        let packet = AudioPacket::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
575
576        assert_eq!(
577            packet,
578            AudioPacket::MultichannelConfig {
579                channel_count: 2,
580                multichannel_config: MultichannelConfigOrder::Unknown(AudioChannelOrder(4)),
581            },
582        );
583    }
584
585    #[test]
586    fn simple_body_demux() {
587        let data = &[
588            42, 42, // data
589        ];
590
591        let header = ExAudioTagHeader {
592            audio_packet_mod_exs: vec![],
593            audio_packet_type: AudioPacketType::CodedFrames,
594            content: ExAudioTagHeaderContent::NoMultiTrack(AudioFourCc::Aac),
595        };
596
597        let packet = ExAudioTagBody::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
598
599        assert_eq!(
600            packet,
601            ExAudioTagBody::NoMultitrack {
602                audio_four_cc: AudioFourCc::Aac,
603                packet: AudioPacket::CodedFrames {
604                    data: Bytes::from_static(data)
605                },
606            },
607        );
608    }
609
610    #[test]
611    fn multitrack_many_codecs_body_demux() {
612        let data = &[
613            b'm', b'p', b'4', b'a', // audio four cc
614            1,    // audio track id
615            0, 0, 2, // size
616            42, 42, // data
617            b'O', b'p', b'u', b's', // audio four cc
618            2,    // audio track id
619            0, 0, 2, // size
620            13, 37, // data
621        ];
622
623        let header = ExAudioTagHeader {
624            audio_packet_mod_exs: vec![],
625            audio_packet_type: AudioPacketType::CodedFrames,
626            content: ExAudioTagHeaderContent::ManyTracksManyCodecs,
627        };
628
629        let packet = ExAudioTagBody::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
630
631        assert_eq!(
632            packet,
633            ExAudioTagBody::ManyTracks(vec![
634                AudioTrack {
635                    audio_four_cc: AudioFourCc::Aac,
636                    audio_track_id: 1,
637                    packet: AudioPacket::CodedFrames {
638                        data: Bytes::from_static(&[42, 42])
639                    },
640                },
641                AudioTrack {
642                    audio_four_cc: AudioFourCc::Opus,
643                    audio_track_id: 2,
644                    packet: AudioPacket::CodedFrames {
645                        data: Bytes::from_static(&[13, 37])
646                    },
647                }
648            ]),
649        );
650    }
651
652    #[test]
653    fn multitrack_body_demux() {
654        let data = &[
655            1, // audio track id
656            0, 0, 2, // size
657            42, 42, // data
658            2,  // audio track id
659            0, 0, 2, // size
660            13, 37, // data
661        ];
662
663        let header = ExAudioTagHeader {
664            audio_packet_mod_exs: vec![],
665            audio_packet_type: AudioPacketType::CodedFrames,
666            content: ExAudioTagHeaderContent::ManyTracks(AudioFourCc::Aac),
667        };
668
669        let packet = ExAudioTagBody::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
670
671        assert_eq!(
672            packet,
673            ExAudioTagBody::ManyTracks(vec![
674                AudioTrack {
675                    audio_four_cc: AudioFourCc::Aac,
676                    audio_track_id: 1,
677                    packet: AudioPacket::CodedFrames {
678                        data: Bytes::from_static(&[42, 42])
679                    },
680                },
681                AudioTrack {
682                    audio_four_cc: AudioFourCc::Aac,
683                    audio_track_id: 2,
684                    packet: AudioPacket::CodedFrames {
685                        data: Bytes::from_static(&[13, 37])
686                    },
687                }
688            ]),
689        );
690    }
691
692    #[test]
693    fn multitrack_one_track_body_demux() {
694        let data = &[
695            1, // audio track id
696            42, 42, // data
697        ];
698
699        let header = ExAudioTagHeader {
700            audio_packet_mod_exs: vec![],
701            audio_packet_type: AudioPacketType::CodedFrames,
702            content: ExAudioTagHeaderContent::OneTrack(AudioFourCc::Aac),
703        };
704
705        let packet = ExAudioTagBody::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
706
707        assert_eq!(
708            packet,
709            ExAudioTagBody::ManyTracks(vec![AudioTrack {
710                audio_four_cc: AudioFourCc::Aac,
711                audio_track_id: 1,
712                packet: AudioPacket::CodedFrames {
713                    data: Bytes::from_static(&[42, 42])
714                },
715            }]),
716        );
717    }
718
719    #[test]
720    fn multitrack_unknown_body_demux() {
721        let data = &[
722            1, // audio track id
723            0, 0, 2, // size
724            42, 42, // data
725        ];
726
727        let header = ExAudioTagHeader {
728            audio_packet_mod_exs: vec![],
729            audio_packet_type: AudioPacketType::CodedFrames,
730            content: ExAudioTagHeaderContent::Unknown {
731                audio_four_cc: AudioFourCc::Aac,
732                audio_multitrack_type: AvMultitrackType(4),
733            },
734        };
735
736        let packet = ExAudioTagBody::demux(&header, &mut std::io::Cursor::new(Bytes::from_static(data))).unwrap();
737
738        assert_eq!(
739            packet,
740            ExAudioTagBody::ManyTracks(vec![AudioTrack {
741                audio_track_id: 1,
742                audio_four_cc: AudioFourCc::Aac,
743                packet: AudioPacket::CodedFrames {
744                    data: Bytes::from_static(&[42, 42])
745                }
746            }]),
747        );
748    }
749}