scuffle_flv/
lib.rs

1//! A pure Rust implementation of the FLV format, allowing for demuxing of FLV
2//! files and streams.
3//!
4//! ## Specifications
5//!
6//! | Name | Version | Link | Comments |
7//! | --- | --- | --- | --- |
8//! | Video File Format Specification | `10` | <https://github.com/veovera/enhanced-rtmp/blob/main/docs/legacy/video-file-format-v10-0-spec.pdf> | |
9//! | Adobe Flash Video File Format Specification | `10.1` | <https://github.com/veovera/enhanced-rtmp/blob/main/docs/legacy/video-file-format-v10-1-spec.pdf> | Refered to as 'Legacy FLV spec' in this documentation |
10//! | Enhancing RTMP, FLV | `v1-2024-02-29-r1` | <https://github.com/veovera/enhanced-rtmp/blob/main/docs/enhanced/enhanced-rtmp-v1.pdf> | |
11//! | Enhanced RTMP | `v2-2024-10-22-b1` | <https://github.com/veovera/enhanced-rtmp/blob/main/docs/enhanced/enhanced-rtmp-v2.pdf> | Refered to as 'Enhanced RTMP spec' in this documentation |
12//!
13//! ## License
14//!
15//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license.
16//! You can choose between one of them if you use this work.
17//!
18//! `SPDX-License-Identifier: MIT OR Apache-2.0`
19#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
20#![deny(missing_docs)]
21#![deny(unsafe_code)]
22#![deny(unreachable_pub)]
23
24pub mod audio;
25pub mod common;
26pub mod error;
27pub mod file;
28pub mod header;
29pub mod script;
30pub mod tag;
31pub mod video;
32
33#[cfg(test)]
34#[cfg_attr(all(test, coverage_nightly), coverage(off))]
35mod tests {
36    use std::io;
37    use std::path::PathBuf;
38
39    use bytes::Bytes;
40    use scuffle_aac::{AudioObjectType, PartialAudioSpecificConfig};
41    use scuffle_amf0::Amf0Value;
42    use scuffle_av1::ObuHeader;
43    use scuffle_av1::seq::SequenceHeaderObu;
44    use scuffle_bytes_util::StringCow;
45    use scuffle_h264::Sps;
46    use scuffle_h265::{ConstantFrameRate, NumTemporalLayers};
47
48    use crate::audio::AudioData;
49    use crate::audio::body::AudioTagBody;
50    use crate::audio::body::legacy::LegacyAudioTagBody;
51    use crate::audio::body::legacy::aac::AacAudioData;
52    use crate::audio::header::AudioTagHeader;
53    use crate::audio::header::legacy::{LegacyAudioTagHeader, SoundFormat, SoundRate, SoundSize, SoundType};
54    use crate::file::FlvFile;
55    use crate::script::{OnMetaDataAudioCodecId, OnMetaDataVideoCodecId, ScriptData};
56    use crate::tag::FlvTagData;
57    use crate::video::VideoData;
58    use crate::video::body::VideoTagBody;
59    use crate::video::body::enhanced::{ExVideoTagBody, VideoPacket, VideoPacketSequenceStart};
60    use crate::video::body::legacy::LegacyVideoTagBody;
61    use crate::video::header::enhanced::VideoFourCc;
62    use crate::video::header::legacy::{LegacyVideoTagHeader, LegacyVideoTagHeaderAvcPacket, VideoCodecId};
63    use crate::video::header::{VideoFrameType, VideoTagHeader, VideoTagHeaderData};
64
65    #[test]
66    fn test_demux_flv_avc_aac() {
67        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
68
69        let data = Bytes::from(std::fs::read(dir.join("avc_aac.flv")).expect("failed to read file"));
70        let mut reader = io::Cursor::new(data);
71
72        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
73
74        assert_eq!(flv.header.version, 1);
75        assert!(flv.header.is_audio_present);
76        assert!(flv.header.is_video_present);
77        assert_eq!(flv.header.extra.len(), 0);
78
79        let mut tags = flv.tags.into_iter();
80
81        // Metadata tag
82        {
83            let tag = tags.next().expect("expected tag");
84            assert_eq!(tag.timestamp_ms, 0);
85            assert_eq!(tag.stream_id, 0);
86
87            // This is a metadata tag
88            let on_meta_data = match tag.data {
89                FlvTagData::ScriptData(ScriptData::OnMetaData(data)) => data,
90                _ => panic!("expected script data"),
91            };
92
93            assert_eq!(on_meta_data.audiosamplesize, Some(16.0));
94            assert_eq!(on_meta_data.audiosamplerate, Some(48000.0));
95            assert_eq!(on_meta_data.stereo, Some(true));
96            assert_eq!(
97                on_meta_data.audiocodecid,
98                Some(OnMetaDataAudioCodecId::Legacy(SoundFormat::Aac))
99            ); // AAC
100            assert_eq!(
101                on_meta_data.videocodecid,
102                Some(OnMetaDataVideoCodecId::Legacy(VideoCodecId::Avc))
103            ); // AVC
104            assert_eq!(on_meta_data.duration, Some(1.088)); // 1.088 seconds
105            assert_eq!(on_meta_data.width, Some(3840.0));
106            assert_eq!(on_meta_data.height, Some(2160.0));
107            assert_eq!(on_meta_data.framerate, Some(60.0));
108            assert!(on_meta_data.videodatarate.is_some());
109            assert!(on_meta_data.audiodatarate.is_some());
110
111            // Should have a minor version property
112            let minor_version = match on_meta_data.other.get(&StringCow::from_static("minor_version")) {
113                Some(Amf0Value::String(number)) => number,
114                _ => panic!("expected minor version"),
115            };
116
117            assert_eq!(minor_version, "512");
118
119            // Should have a major brand property
120            let major_brand = match on_meta_data.other.get(&StringCow::from_static("major_brand")) {
121                Some(Amf0Value::String(string)) => string,
122                _ => panic!("expected major brand"),
123            };
124
125            assert_eq!(major_brand, "iso5");
126
127            // Should have a compatible_brands property
128            let compatible_brands = match on_meta_data.other.get(&StringCow::from_static("compatible_brands")) {
129                Some(Amf0Value::String(string)) => string,
130                _ => panic!("expected compatible brands"),
131            };
132
133            assert_eq!(compatible_brands, "iso5iso6mp41");
134        }
135
136        // Video Sequence Header Tag
137        {
138            let tag = tags.next().expect("expected tag");
139            assert_eq!(tag.timestamp_ms, 0);
140            assert_eq!(tag.stream_id, 0);
141
142            // This is a video tag
143            let (frame_type, avc_decoder_configuration_record) = match tag.data {
144                FlvTagData::Video(VideoData {
145                    header: VideoTagHeader { frame_type, .. },
146                    body: VideoTagBody::Legacy(LegacyVideoTagBody::AvcVideoPacketSeqHdr(avc_decoder_configuration_record)),
147                }) => (frame_type, avc_decoder_configuration_record),
148                _ => panic!("expected video data"),
149            };
150
151            assert_eq!(frame_type, VideoFrameType::KeyFrame);
152
153            // The avc sequence header should be able to be decoded into an avc decoder
154            // configuration record
155            assert_eq!(avc_decoder_configuration_record.profile_indication, 100);
156            assert_eq!(avc_decoder_configuration_record.profile_compatibility, 0);
157            assert_eq!(avc_decoder_configuration_record.level_indication, 51); // 5.1
158            assert_eq!(avc_decoder_configuration_record.length_size_minus_one, 3);
159            assert_eq!(avc_decoder_configuration_record.sps.len(), 1);
160            assert_eq!(avc_decoder_configuration_record.pps.len(), 1);
161            assert_eq!(avc_decoder_configuration_record.extended_config, None);
162
163            let sps =
164                Sps::parse_with_emulation_prevention(&mut std::io::Cursor::new(&avc_decoder_configuration_record.sps[0]))
165                    .expect("expected sequence parameter set");
166
167            insta::assert_debug_snapshot!(sps, @r"
168            Sps {
169                nal_ref_idc: 3,
170                nal_unit_type: NALUnitType::SPS,
171                profile_idc: 100,
172                constraint_set0_flag: false,
173                constraint_set1_flag: false,
174                constraint_set2_flag: false,
175                constraint_set3_flag: false,
176                constraint_set4_flag: false,
177                constraint_set5_flag: false,
178                level_idc: 51,
179                seq_parameter_set_id: 0,
180                ext: Some(
181                    SpsExtended {
182                        chroma_format_idc: 1,
183                        separate_color_plane_flag: false,
184                        bit_depth_luma_minus8: 0,
185                        bit_depth_chroma_minus8: 0,
186                        qpprime_y_zero_transform_bypass_flag: false,
187                        scaling_matrix: [],
188                    },
189                ),
190                log2_max_frame_num_minus4: 0,
191                pic_order_cnt_type: 0,
192                log2_max_pic_order_cnt_lsb_minus4: Some(
193                    4,
194                ),
195                pic_order_cnt_type1: None,
196                max_num_ref_frames: 4,
197                gaps_in_frame_num_value_allowed_flag: false,
198                pic_width_in_mbs_minus1: 239,
199                pic_height_in_map_units_minus1: 134,
200                mb_adaptive_frame_field_flag: None,
201                direct_8x8_inference_flag: true,
202                frame_crop_info: None,
203                sample_aspect_ratio: Some(
204                    SarDimensions {
205                        aspect_ratio_idc: AspectRatioIdc::Square,
206                        sar_width: 0,
207                        sar_height: 0,
208                    },
209                ),
210                overscan_appropriate_flag: None,
211                color_config: None,
212                chroma_sample_loc: None,
213                timing_info: Some(
214                    TimingInfo {
215                        num_units_in_tick: 1,
216                        time_scale: 120,
217                    },
218                ),
219            }
220            ");
221        }
222
223        // Audio Sequence Header Tag
224        {
225            let tag = tags.next().expect("expected tag");
226            assert_eq!(tag.timestamp_ms, 0);
227            assert_eq!(tag.stream_id, 0);
228
229            let (data, sound_rate, sound_size, sound_type) = match tag.data {
230                FlvTagData::Audio(AudioData {
231                    header:
232                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
233                            sound_rate,
234                            sound_size,
235                            sound_type,
236                            ..
237                        }),
238                    body,
239                }) => (body, sound_rate, sound_size, sound_type),
240                _ => panic!("expected audio data"),
241            };
242
243            assert_eq!(sound_rate, SoundRate::Hz44000);
244            assert_eq!(sound_size, SoundSize::Bit16);
245            assert_eq!(sound_type, SoundType::Stereo);
246
247            // Audio data should be an AAC sequence header
248            let data = match data {
249                AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::SequenceHeader(data))) => data,
250                _ => panic!("expected aac sequence header"),
251            };
252
253            // The aac sequence header should be able to be decoded into an aac decoder
254            // configuration record
255            let aac_decoder_configuration_record =
256                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
257
258            assert_eq!(
259                aac_decoder_configuration_record.audio_object_type,
260                AudioObjectType::AacLowComplexity
261            );
262            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
263            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
264        }
265
266        // Rest of the tags should be video / audio data
267        let mut last_timestamp = 0;
268        let mut read_seq_end = false;
269        for tag in tags {
270            assert!(tag.timestamp_ms >= last_timestamp);
271            assert_eq!(tag.stream_id, 0);
272
273            last_timestamp = tag.timestamp_ms;
274
275            match tag.data {
276                FlvTagData::Audio(AudioData {
277                    body,
278                    header:
279                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
280                            sound_rate,
281                            sound_size,
282                            sound_type,
283                            ..
284                        }),
285                }) => {
286                    assert_eq!(sound_rate, SoundRate::Hz44000);
287                    assert_eq!(sound_size, SoundSize::Bit16);
288                    assert_eq!(sound_type, SoundType::Stereo);
289                    match body {
290                        AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::Raw(data))) => data,
291                        _ => panic!("expected aac raw packet"),
292                    };
293                }
294                FlvTagData::Video(VideoData {
295                    header:
296                        VideoTagHeader {
297                            frame_type,
298                            data: VideoTagHeaderData::Legacy(data),
299                        },
300                    ..
301                }) => {
302                    match frame_type {
303                        VideoFrameType::KeyFrame => (),
304                        VideoFrameType::InterFrame => (),
305                        _ => panic!("expected keyframe or interframe"),
306                    }
307
308                    match data {
309                        LegacyVideoTagHeader::AvcPacket(LegacyVideoTagHeaderAvcPacket::Nalu { .. }) => {
310                            assert!(!read_seq_end)
311                        }
312                        LegacyVideoTagHeader::AvcPacket(LegacyVideoTagHeaderAvcPacket::EndOfSequence) => {
313                            assert!(!read_seq_end);
314                            read_seq_end = true;
315                        }
316                        _ => panic!("expected avc nalu packet: {data:?}"),
317                    }
318                }
319                _ => panic!("unexpected data"),
320            };
321        }
322
323        assert!(read_seq_end);
324    }
325
326    #[test]
327    fn test_demux_flv_av1_aac() {
328        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
329
330        let data = Bytes::from(std::fs::read(dir.join("av1_aac.flv")).expect("failed to read file"));
331        let mut reader = io::Cursor::new(data);
332
333        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
334
335        assert_eq!(flv.header.version, 1);
336        assert!(flv.header.is_audio_present);
337        assert!(flv.header.is_video_present);
338        assert_eq!(flv.header.extra.len(), 0);
339
340        let mut tags = flv.tags.into_iter();
341
342        // Metadata tag
343        {
344            let tag = tags.next().expect("expected tag");
345            assert_eq!(tag.timestamp_ms, 0);
346            assert_eq!(tag.stream_id, 0);
347
348            // This is a metadata tag
349            let on_meta_data = match tag.data {
350                FlvTagData::ScriptData(ScriptData::OnMetaData(data)) => data,
351                _ => panic!("expected script data"),
352            };
353
354            assert_eq!(on_meta_data.audiosamplesize, Some(16.0));
355            assert_eq!(on_meta_data.audiosamplerate, Some(48000.0));
356            assert_eq!(on_meta_data.stereo, Some(true));
357            assert_eq!(
358                on_meta_data.audiocodecid,
359                Some(OnMetaDataAudioCodecId::Legacy(SoundFormat::Aac))
360            ); // AAC
361            assert_eq!(
362                on_meta_data.videocodecid,
363                Some(OnMetaDataVideoCodecId::Legacy(VideoCodecId::Avc))
364            ); // AVC
365            assert_eq!(on_meta_data.duration, Some(0.0)); // 0 seconds (this was a live stream)
366            assert_eq!(on_meta_data.width, Some(2560.0));
367            assert_eq!(on_meta_data.height, Some(1440.0));
368            assert_eq!(on_meta_data.framerate, Some(144.0));
369            assert!(on_meta_data.videodatarate.is_some());
370            assert!(on_meta_data.audiodatarate.is_some());
371        }
372
373        // Audio Sequence Header Tag
374        {
375            let tag = tags.next().expect("expected tag");
376            assert_eq!(tag.timestamp_ms, 0);
377            assert_eq!(tag.stream_id, 0);
378
379            let (body, sound_rate, sound_size, sound_type) = match tag.data {
380                FlvTagData::Audio(AudioData {
381                    body,
382                    header:
383                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
384                            sound_rate,
385                            sound_size,
386                            sound_type,
387                            ..
388                        }),
389                }) => (body, sound_rate, sound_size, sound_type),
390                _ => panic!("expected audio data"),
391            };
392
393            assert_eq!(sound_rate, SoundRate::Hz44000);
394            assert_eq!(sound_size, SoundSize::Bit16);
395            assert_eq!(sound_type, SoundType::Stereo);
396
397            // Audio data should be an AAC sequence header
398            let data = match body {
399                AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::SequenceHeader(data))) => data,
400                _ => panic!("expected aac sequence header"),
401            };
402
403            // The aac sequence header should be able to be decoded into an aac decoder
404            // configuration record
405            let aac_decoder_configuration_record =
406                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
407
408            assert_eq!(
409                aac_decoder_configuration_record.audio_object_type,
410                AudioObjectType::AacLowComplexity
411            );
412            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
413            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
414        }
415
416        // Video Sequence Header Tag
417        {
418            let tag = tags.next().expect("expected tag");
419            assert_eq!(tag.timestamp_ms, 0);
420            assert_eq!(tag.stream_id, 0);
421
422            // This is a video tag
423            let frame_type = match tag.data {
424                FlvTagData::Video(VideoData {
425                    header: VideoTagHeader { frame_type, .. },
426                    ..
427                }) => frame_type,
428                _ => panic!("expected video data"),
429            };
430
431            assert_eq!(frame_type, VideoFrameType::KeyFrame);
432
433            // Video data should be an AVC sequence header
434            let config = match tag.data {
435                FlvTagData::Video(VideoData {
436                    body:
437                        VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
438                            video_four_cc: VideoFourCc::Av1,
439                            packet: VideoPacket::SequenceStart(VideoPacketSequenceStart::Av1(config)),
440                        }),
441                    ..
442                }) => config,
443                _ => panic!("expected video data"),
444            };
445
446            assert_eq!(config.chroma_sample_position, 0);
447            assert!(config.chroma_subsampling_x); // 5.1
448            assert!(config.chroma_subsampling_y);
449            assert!(!config.high_bitdepth);
450            assert!(!config.twelve_bit);
451
452            let mut reader = std::io::Cursor::new(config.config_obu);
453
454            let header = ObuHeader::parse(&mut reader).expect("expected obu header");
455
456            let seq_obu = SequenceHeaderObu::parse(header, &mut reader).expect("expected sequence obu");
457
458            assert_eq!(seq_obu.max_frame_height, 1440);
459            assert_eq!(seq_obu.max_frame_width, 2560);
460        }
461
462        // Rest of the tags should be video / audio data
463        let mut last_timestamp = 0;
464        let mut read_seq_end = false;
465        for tag in tags {
466            assert!(tag.timestamp_ms >= last_timestamp || tag.timestamp_ms == 0); // Timestamps should be monotonically increasing or 0
467            assert_eq!(tag.stream_id, 0);
468
469            if tag.timestamp_ms != 0 {
470                last_timestamp = tag.timestamp_ms;
471            }
472
473            match tag.data {
474                FlvTagData::Audio(AudioData {
475                    body,
476                    header:
477                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
478                            sound_rate,
479                            sound_size,
480                            sound_type,
481                            ..
482                        }),
483                }) => {
484                    assert_eq!(sound_rate, SoundRate::Hz44000);
485                    assert_eq!(sound_size, SoundSize::Bit16);
486                    assert_eq!(sound_type, SoundType::Stereo);
487                    match body {
488                        AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::Raw(data))) => data,
489                        _ => panic!("expected aac raw packet"),
490                    };
491                }
492                FlvTagData::Video(VideoData {
493                    header: VideoTagHeader { frame_type, .. },
494                    body: VideoTagBody::Enhanced(body),
495                }) => {
496                    match frame_type {
497                        VideoFrameType::KeyFrame => (),
498                        VideoFrameType::InterFrame => (),
499                        _ => panic!("expected keyframe or interframe"),
500                    }
501
502                    match body {
503                        ExVideoTagBody::NoMultitrack {
504                            video_four_cc: VideoFourCc::Av1,
505                            packet: VideoPacket::CodedFrames(_),
506                        } => {
507                            assert!(!read_seq_end);
508                        }
509                        ExVideoTagBody::NoMultitrack {
510                            video_four_cc: VideoFourCc::Av1,
511                            packet: VideoPacket::CodedFramesX { .. },
512                        } => {
513                            assert!(!read_seq_end);
514                        }
515                        ExVideoTagBody::ManyTracks(tracks) => {
516                            assert!(!read_seq_end);
517                            assert!(tracks.is_empty());
518                        }
519                        ExVideoTagBody::NoMultitrack {
520                            video_four_cc: VideoFourCc::Av1,
521                            packet: VideoPacket::SequenceEnd,
522                        } => {
523                            assert!(!read_seq_end);
524                            read_seq_end = true;
525                        }
526                        _ => panic!("expected av1 raw packet: {body:?}"),
527                    };
528                }
529                _ => panic!("unexpected data"),
530            };
531        }
532
533        assert!(read_seq_end);
534    }
535
536    #[test]
537    fn test_demux_flv_hevc_aac() {
538        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
539
540        let data = Bytes::from(std::fs::read(dir.join("hevc_aac.flv")).expect("failed to read file"));
541        let mut reader = io::Cursor::new(data);
542
543        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
544
545        assert_eq!(flv.header.version, 1);
546        assert!(flv.header.is_audio_present);
547        assert!(flv.header.is_video_present);
548        assert_eq!(flv.header.extra.len(), 0);
549
550        let mut tags = flv.tags.into_iter();
551
552        // Metadata tag
553        {
554            let tag = tags.next().expect("expected tag");
555            assert_eq!(tag.timestamp_ms, 0);
556            assert_eq!(tag.stream_id, 0);
557
558            let on_meta_data = match tag.data {
559                FlvTagData::ScriptData(ScriptData::OnMetaData(data)) => data,
560                _ => panic!("expected script data"),
561            };
562
563            assert_eq!(on_meta_data.audiosamplesize, Some(16.0));
564            assert_eq!(on_meta_data.audiosamplerate, Some(48000.0));
565            assert_eq!(on_meta_data.stereo, Some(true));
566            assert_eq!(
567                on_meta_data.audiocodecid,
568                Some(OnMetaDataAudioCodecId::Legacy(SoundFormat::Aac))
569            ); // AAC
570            assert_eq!(
571                on_meta_data.videocodecid,
572                Some(OnMetaDataVideoCodecId::Enhanced(VideoFourCc::Hevc))
573            ); // HEVC
574            assert_eq!(on_meta_data.duration, Some(2.038));
575            assert_eq!(on_meta_data.width, Some(3840.0));
576            assert_eq!(on_meta_data.height, Some(2160.0));
577            assert_eq!(on_meta_data.framerate, Some(60.0));
578            assert!(on_meta_data.videodatarate.is_some());
579            assert!(on_meta_data.audiodatarate.is_some());
580        }
581
582        // Video Sequence Header Tag
583        {
584            let tag = tags.next().expect("expected tag");
585            assert_eq!(tag.timestamp_ms, 0);
586            assert_eq!(tag.stream_id, 0);
587
588            // This is a video tag
589            let (frame_type, config) = match tag.data {
590                FlvTagData::Video(VideoData {
591                    header: VideoTagHeader { frame_type, .. },
592                    body:
593                        VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
594                            video_four_cc: VideoFourCc::Hevc,
595                            packet: VideoPacket::SequenceStart(VideoPacketSequenceStart::Hevc(config)),
596                        }),
597                }) => (frame_type, config),
598                _ => panic!("expected video data"),
599            };
600
601            assert_eq!(frame_type, VideoFrameType::KeyFrame);
602
603            assert_eq!(config.avg_frame_rate, 0);
604            assert_eq!(config.constant_frame_rate, ConstantFrameRate::Unknown);
605            assert_eq!(config.num_temporal_layers, NumTemporalLayers::NotScalable);
606
607            // We should be able to find a SPS NAL unit in the sequence header
608            let Some(sps) = config
609                .arrays
610                .iter()
611                .find(|a| a.nal_unit_type == scuffle_h265::NALUnitType::SpsNut)
612                .and_then(|v| v.nalus.first())
613            else {
614                panic!("expected sps");
615            };
616
617            // We should be able to find a PPS NAL unit in the sequence header
618            let Some(_) = config
619                .arrays
620                .iter()
621                .find(|a| a.nal_unit_type == scuffle_h265::NALUnitType::PpsNut)
622                .and_then(|v| v.nalus.first())
623            else {
624                panic!("expected pps");
625            };
626
627            // We should be able to decode the SPS NAL unit
628            let sps = scuffle_h265::SpsNALUnit::parse(io::Cursor::new(sps.clone())).expect("expected sps");
629
630            insta::assert_debug_snapshot!(sps);
631        }
632
633        // Audio Sequence Header Tag
634        {
635            let tag = tags.next().expect("expected tag");
636            assert_eq!(tag.timestamp_ms, 0);
637            assert_eq!(tag.stream_id, 0);
638
639            let (body, sound_rate, sound_size, sound_type) = match tag.data {
640                FlvTagData::Audio(AudioData {
641                    body,
642                    header:
643                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
644                            sound_rate,
645                            sound_size,
646                            sound_type,
647                            ..
648                        }),
649                }) => (body, sound_rate, sound_size, sound_type),
650                _ => panic!("expected audio data"),
651            };
652
653            assert_eq!(sound_rate, SoundRate::Hz44000);
654            assert_eq!(sound_size, SoundSize::Bit16);
655            assert_eq!(sound_type, SoundType::Stereo);
656
657            // Audio data should be an AAC sequence header
658            let data = match body {
659                AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::SequenceHeader(data))) => data,
660                _ => panic!("expected aac sequence header"),
661            };
662
663            // The aac sequence header should be able to be decoded into an aac decoder
664            // configuration record
665            let aac_decoder_configuration_record =
666                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
667
668            assert_eq!(
669                aac_decoder_configuration_record.audio_object_type,
670                AudioObjectType::AacLowComplexity
671            );
672            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
673            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
674        }
675
676        // Rest of the tags should be video / audio data
677        let mut last_timestamp = 0;
678        for tag in tags {
679            assert!(tag.timestamp_ms >= last_timestamp || tag.timestamp_ms == 0); // Timestamps should be monotonically increasing or 0
680            assert_eq!(tag.stream_id, 0);
681
682            if tag.timestamp_ms != 0 {
683                last_timestamp = tag.timestamp_ms;
684            }
685
686            match tag.data {
687                FlvTagData::Audio(AudioData {
688                    body,
689                    header:
690                        AudioTagHeader::Legacy(LegacyAudioTagHeader {
691                            sound_rate,
692                            sound_size,
693                            sound_type,
694                            ..
695                        }),
696                }) => {
697                    assert_eq!(sound_rate, SoundRate::Hz44000);
698                    assert_eq!(sound_size, SoundSize::Bit16);
699                    assert_eq!(sound_type, SoundType::Stereo);
700                    match body {
701                        AudioTagBody::Legacy(LegacyAudioTagBody::Aac(AacAudioData::Raw(data))) => data,
702                        _ => panic!("expected aac raw packet"),
703                    };
704                }
705                FlvTagData::Video(VideoData {
706                    header: VideoTagHeader { frame_type, .. },
707                    body:
708                        VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
709                            video_four_cc: VideoFourCc::Hevc,
710                            ..
711                        }),
712                }) => match frame_type {
713                    VideoFrameType::KeyFrame => (),
714                    VideoFrameType::InterFrame => (),
715                    VideoFrameType::Command => (),
716                    _ => panic!("expected keyframe, interframe or command"),
717                },
718                _ => panic!("unexpected data"),
719            };
720        }
721    }
722}