scuffle_flv/
lib.rs

1//! A pure Rust implementation of the FLV format, allowing for demuxing of FLV
2//! files or streams.
3//!
4//! This does not support all FLV features (mainly those from FLV 10.1), however
5//! it does support some newer features, from the enhanced FLV specification.
6//!
7//! ## Specifications
8//!
9//! - <https://github.com/veovera/enhanced-rtmp/blob/main/docs/legacy/video-file-format-v10-0-spec.pdf>
10//! - <https://github.com/veovera/enhanced-rtmp/blob/main/docs/legacy/video-file-format-v10-1-spec.pdf>
11//! - <https://github.com/veovera/enhanced-rtmp/blob/main/docs/enhanced/enhanced-rtmp-v1.pdf>
12//! - <https://github.com/veovera/enhanced-rtmp/blob/main/docs/enhanced/enhanced-rtmp-v2.pdf>
13//!
14//! ## License
15//!
16//! This project is licensed under the [MIT](./LICENSE.MIT) or
17//! [Apache-2.0](./LICENSE.Apache-2.0) license. You can choose between one of
18//! them if you use this work.
19//!
20//! `SPDX-License-Identifier: MIT OR Apache-2.0`
21#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
22
23pub mod aac;
24pub mod audio;
25pub mod av1;
26pub mod avc;
27pub mod file;
28pub mod header;
29pub mod hevc;
30pub mod script;
31pub mod tag;
32pub mod video;
33
34pub use crate::file::FlvFile;
35pub use crate::header::FlvHeader;
36pub use crate::tag::{FlvTag, FlvTagData, FlvTagType};
37
38#[cfg(test)]
39#[cfg_attr(all(test, coverage_nightly), coverage(off))]
40mod tests {
41    use std::collections::HashMap;
42    use std::io;
43    use std::path::PathBuf;
44
45    use bytes::Bytes;
46    use scuffle_aac::{AudioObjectType, PartialAudioSpecificConfig};
47    use scuffle_amf0::Amf0Value;
48    use scuffle_av1::seq::SequenceHeaderObu;
49    use scuffle_av1::ObuHeader;
50    use scuffle_h264::{Sps, SpsExtended};
51
52    use crate::aac::AacPacket;
53    use crate::audio::{AudioData, AudioDataBody, SoundRate, SoundSize, SoundType};
54    use crate::av1::Av1Packet;
55    use crate::avc::AvcPacket;
56    use crate::file::FlvFile;
57    use crate::hevc::HevcPacket;
58    use crate::script::ScriptData;
59    use crate::tag::FlvTagData;
60    use crate::video::{EnhancedPacket, FrameType, VideoFourCC, VideoTagBody, VideoTagHeader};
61
62    #[test]
63    fn test_demux_flv_avc_aac() {
64        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
65
66        let data = Bytes::from(std::fs::read(dir.join("avc_aac.flv")).expect("failed to read file"));
67        let mut reader = io::Cursor::new(data);
68
69        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
70
71        assert_eq!(flv.header.version, 1);
72        assert!(flv.header.has_audio);
73        assert!(flv.header.has_video);
74        assert_eq!(flv.header.extra.len(), 0);
75
76        let mut tags = flv.tags.into_iter();
77
78        // Metadata tag
79        {
80            let tag = tags.next().expect("expected tag");
81            assert_eq!(tag.timestamp_ms, 0);
82            assert_eq!(tag.stream_id, 0);
83
84            // This is a metadata tag
85            let script_data = match tag.data {
86                FlvTagData::ScriptData(ScriptData { name, data }) => {
87                    assert_eq!(name, "onMetaData");
88                    data
89                }
90                _ => panic!("expected script data"),
91            };
92
93            // Script data should be an AMF0 object
94            let object = match &script_data[0] {
95                Amf0Value::Object(object) => object,
96                _ => panic!("expected object"),
97            };
98
99            let object = object.iter().map(|(k, v)| (k.as_ref(), v)).collect::<HashMap<_, _>>();
100
101            // Should have a audio sample size property
102            let audio_sample_size = match object.get("audiosamplesize") {
103                Some(Amf0Value::Number(number)) => number,
104                _ => panic!("expected audio sample size"),
105            };
106
107            assert_eq!(audio_sample_size, &16.0);
108
109            // Should have a audio sample rate property
110            let audio_sample_rate = match object.get("audiosamplerate") {
111                Some(Amf0Value::Number(number)) => number,
112                _ => panic!("expected audio sample rate"),
113            };
114
115            assert_eq!(audio_sample_rate, &48000.0);
116
117            // Should have a stereo property
118            let stereo = match object.get("stereo") {
119                Some(Amf0Value::Boolean(boolean)) => boolean,
120                _ => panic!("expected stereo"),
121            };
122
123            assert_eq!(stereo, &true);
124
125            // Should have an audio codec id property
126            let audio_codec_id = match object.get("audiocodecid") {
127                Some(Amf0Value::Number(number)) => number,
128                _ => panic!("expected audio codec id"),
129            };
130
131            assert_eq!(audio_codec_id, &10.0); // AAC
132
133            // Should have a video codec id property
134            let video_codec_id = match object.get("videocodecid") {
135                Some(Amf0Value::Number(number)) => number,
136                _ => panic!("expected video codec id"),
137            };
138
139            assert_eq!(video_codec_id, &7.0); // AVC
140
141            // Should have a duration property
142            let duration = match object.get("duration") {
143                Some(Amf0Value::Number(number)) => number,
144                _ => panic!("expected duration"),
145            };
146
147            assert_eq!(duration, &1.088); // 1.088 seconds
148
149            // Should have a width property
150            let width = match object.get("width") {
151                Some(Amf0Value::Number(number)) => number,
152                _ => panic!("expected width"),
153            };
154
155            assert_eq!(width, &3840.0);
156
157            // Should have a height property
158            let height = match object.get("height") {
159                Some(Amf0Value::Number(number)) => number,
160                _ => panic!("expected height"),
161            };
162
163            assert_eq!(height, &2160.0);
164
165            // Should have a framerate property
166            let framerate = match object.get("framerate") {
167                Some(Amf0Value::Number(number)) => number,
168                _ => panic!("expected framerate"),
169            };
170
171            assert_eq!(framerate, &60.0);
172
173            // Should have a videodatarate property
174            match object.get("videodatarate") {
175                Some(Amf0Value::Number(number)) => number,
176                _ => panic!("expected videodatarate"),
177            };
178
179            // Should have a audiodatarate property
180            match object.get("audiodatarate") {
181                Some(Amf0Value::Number(number)) => number,
182                _ => panic!("expected audiodatarate"),
183            };
184
185            // Should have a minor version property
186            let minor_version = match object.get("minor_version") {
187                Some(Amf0Value::String(number)) => number,
188                _ => panic!("expected minor version"),
189            };
190
191            assert_eq!(minor_version, "512");
192
193            // Should have a major brand property
194            let major_brand = match object.get("major_brand") {
195                Some(Amf0Value::String(string)) => string,
196                _ => panic!("expected major brand"),
197            };
198
199            assert_eq!(major_brand, "iso5");
200
201            // Should have a compatible_brands property
202            let compatible_brands = match object.get("compatible_brands") {
203                Some(Amf0Value::String(string)) => string,
204                _ => panic!("expected compatible brands"),
205            };
206
207            assert_eq!(compatible_brands, "iso5iso6mp41");
208        }
209
210        // Video Sequence Header Tag
211        {
212            let tag = tags.next().expect("expected tag");
213            assert_eq!(tag.timestamp_ms, 0);
214            assert_eq!(tag.stream_id, 0);
215
216            // This is a video tag
217            let (frame_type, video_data) = match tag.data {
218                FlvTagData::Video(VideoTagHeader { frame_type, body }) => (frame_type, body),
219                _ => panic!("expected video data"),
220            };
221
222            assert_eq!(frame_type, FrameType::Keyframe);
223
224            // Video data should be an AVC sequence header
225            let avc_decoder_configuration_record = match video_data {
226                VideoTagBody::Avc(AvcPacket::SequenceHeader(data)) => data,
227                _ => panic!("expected avc sequence header"),
228            };
229
230            // The avc sequence header should be able to be decoded into an avc decoder
231            // configuration record
232            assert_eq!(avc_decoder_configuration_record.profile_indication, 100);
233            assert_eq!(avc_decoder_configuration_record.profile_compatibility, 0);
234            assert_eq!(avc_decoder_configuration_record.level_indication, 51); // 5.1
235            assert_eq!(avc_decoder_configuration_record.length_size_minus_one, 3);
236            assert_eq!(avc_decoder_configuration_record.sps.len(), 1);
237            assert_eq!(avc_decoder_configuration_record.pps.len(), 1);
238            assert_eq!(avc_decoder_configuration_record.extended_config, None);
239
240            let sps = &avc_decoder_configuration_record.sps[0];
241            // SPS should be able to be decoded into a sequence parameter set
242            let sps = Sps::parse(sps.clone()).expect("expected sequence parameter set");
243
244            assert_eq!(sps.profile_idc, 100);
245            assert_eq!(sps.level_idc, 51);
246            assert_eq!(sps.width, 3840);
247            assert_eq!(sps.height, 2160);
248            assert_eq!(sps.frame_rate, 60.0);
249
250            assert_eq!(
251                sps.ext,
252                Some(SpsExtended {
253                    chroma_format_idc: 1,
254                    bit_depth_luma_minus8: 0,
255                    bit_depth_chroma_minus8: 0,
256                })
257            )
258        }
259
260        // Audio Sequence Header Tag
261        {
262            let tag = tags.next().expect("expected tag");
263            assert_eq!(tag.timestamp_ms, 0);
264            assert_eq!(tag.stream_id, 0);
265
266            let (data, sound_rate, sound_size, sound_type) = match tag.data {
267                FlvTagData::Audio(AudioData {
268                    sound_rate,
269                    sound_size,
270                    sound_type,
271                    body,
272                }) => (body, sound_rate, sound_size, sound_type),
273                _ => panic!("expected audio data"),
274            };
275
276            assert_eq!(sound_rate, SoundRate::Hz44000);
277            assert_eq!(sound_size, SoundSize::Bit16);
278            assert_eq!(sound_type, SoundType::Stereo);
279
280            // Audio data should be an AAC sequence header
281            let data = match data {
282                AudioDataBody::Aac(AacPacket::SequenceHeader(data)) => data,
283                _ => panic!("expected aac sequence header"),
284            };
285
286            // The aac sequence header should be able to be decoded into an aac decoder
287            // configuration record
288            let aac_decoder_configuration_record =
289                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
290
291            assert_eq!(
292                aac_decoder_configuration_record.audio_object_type,
293                AudioObjectType::AacLowComplexity
294            );
295            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
296            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
297        }
298
299        // Rest of the tags should be video / audio data
300        let mut last_timestamp = 0;
301        let mut read_seq_end = false;
302        for tag in tags {
303            assert!(tag.timestamp_ms >= last_timestamp);
304            assert_eq!(tag.stream_id, 0);
305
306            last_timestamp = tag.timestamp_ms;
307
308            match tag.data {
309                FlvTagData::Audio(AudioData {
310                    body,
311                    sound_rate,
312                    sound_size,
313                    sound_type,
314                }) => {
315                    assert_eq!(sound_rate, SoundRate::Hz44000);
316                    assert_eq!(sound_size, SoundSize::Bit16);
317                    assert_eq!(sound_type, SoundType::Stereo);
318                    match body {
319                        AudioDataBody::Aac(AacPacket::Raw(data)) => data,
320                        _ => panic!("expected aac raw packet"),
321                    };
322                }
323                FlvTagData::Video(VideoTagHeader { frame_type, body }) => {
324                    match frame_type {
325                        FrameType::Keyframe => (),
326                        FrameType::Interframe => (),
327                        _ => panic!("expected keyframe or interframe"),
328                    }
329
330                    match body {
331                        VideoTagBody::Avc(AvcPacket::Nalu { .. }) => assert!(!read_seq_end),
332                        VideoTagBody::Avc(AvcPacket::EndOfSequence) => {
333                            assert!(!read_seq_end);
334                            read_seq_end = true;
335                        }
336                        _ => panic!("expected avc nalu packet: {:?}", body),
337                    };
338                }
339                _ => panic!("expected audio data"),
340            };
341        }
342
343        assert!(read_seq_end);
344    }
345
346    #[test]
347    fn test_demux_flv_av1_aac() {
348        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
349
350        let data = Bytes::from(std::fs::read(dir.join("av1_aac.flv")).expect("failed to read file"));
351        let mut reader = io::Cursor::new(data);
352
353        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
354
355        assert_eq!(flv.header.version, 1);
356        assert!(flv.header.has_audio);
357        assert!(flv.header.has_video);
358        assert_eq!(flv.header.extra.len(), 0);
359
360        let mut tags = flv.tags.into_iter();
361
362        // Metadata tag
363        {
364            let tag = tags.next().expect("expected tag");
365            assert_eq!(tag.timestamp_ms, 0);
366            assert_eq!(tag.stream_id, 0);
367
368            // This is a metadata tag
369            let script_data = match tag.data {
370                FlvTagData::ScriptData(ScriptData { name, data }) => {
371                    assert_eq!(name, "onMetaData");
372                    data
373                }
374                _ => panic!("expected script data"),
375            };
376
377            // Script data should be an AMF0 object
378            let object = match &script_data[0] {
379                Amf0Value::Object(object) => object,
380                _ => panic!("expected object"),
381            };
382
383            let object = object.iter().map(|(k, v)| (k.as_ref(), v)).collect::<HashMap<_, _>>();
384
385            // Should have a audio sample size property
386            let audio_sample_size = match object.get("audiosamplesize") {
387                Some(Amf0Value::Number(number)) => number,
388                _ => panic!("expected audio sample size"),
389            };
390
391            assert_eq!(audio_sample_size, &16.0);
392
393            // Should have a audio sample rate property
394            let audio_sample_rate = match object.get("audiosamplerate") {
395                Some(Amf0Value::Number(number)) => number,
396                _ => panic!("expected audio sample rate"),
397            };
398
399            assert_eq!(audio_sample_rate, &48000.0);
400
401            // Should have a stereo property
402            let stereo = match object.get("stereo") {
403                Some(Amf0Value::Boolean(boolean)) => boolean,
404                _ => panic!("expected stereo"),
405            };
406
407            assert_eq!(stereo, &true);
408
409            // Should have an audio codec id property
410            let audio_codec_id = match object.get("audiocodecid") {
411                Some(Amf0Value::Number(number)) => number,
412                _ => panic!("expected audio codec id"),
413            };
414
415            assert_eq!(audio_codec_id, &10.0); // AAC
416
417            // Should have a video codec id property
418            let video_codec_id = match object.get("videocodecid") {
419                Some(Amf0Value::Number(number)) => number,
420                _ => panic!("expected video codec id"),
421            };
422
423            assert_eq!(video_codec_id, &7.0); // AVC
424
425            // Should have a duration property
426            let duration = match object.get("duration") {
427                Some(Amf0Value::Number(number)) => number,
428                _ => panic!("expected duration"),
429            };
430
431            assert_eq!(duration, &0.0); // 0 seconds (this was a live stream)
432
433            // Should have a width property
434            let width = match object.get("width") {
435                Some(Amf0Value::Number(number)) => number,
436                _ => panic!("expected width"),
437            };
438
439            assert_eq!(width, &2560.0);
440
441            // Should have a height property
442            let height = match object.get("height") {
443                Some(Amf0Value::Number(number)) => number,
444                _ => panic!("expected height"),
445            };
446
447            assert_eq!(height, &1440.0);
448
449            // Should have a framerate property
450            let framerate = match object.get("framerate") {
451                Some(Amf0Value::Number(number)) => number,
452                _ => panic!("expected framerate"),
453            };
454
455            assert_eq!(framerate, &144.0);
456
457            // Should have a videodatarate property
458            match object.get("videodatarate") {
459                Some(Amf0Value::Number(number)) => number,
460                _ => panic!("expected videodatarate"),
461            };
462
463            // Should have a audiodatarate property
464            match object.get("audiodatarate") {
465                Some(Amf0Value::Number(number)) => number,
466                _ => panic!("expected audiodatarate"),
467            };
468        }
469
470        // Audio Sequence Header Tag
471        {
472            let tag = tags.next().expect("expected tag");
473            assert_eq!(tag.timestamp_ms, 0);
474            assert_eq!(tag.stream_id, 0);
475
476            let (body, sound_rate, sound_size, sound_type) = match tag.data {
477                FlvTagData::Audio(AudioData {
478                    body,
479                    sound_rate,
480                    sound_size,
481                    sound_type,
482                }) => (body, sound_rate, sound_size, sound_type),
483                _ => panic!("expected audio data"),
484            };
485
486            assert_eq!(sound_rate, SoundRate::Hz44000);
487            assert_eq!(sound_size, SoundSize::Bit16);
488            assert_eq!(sound_type, SoundType::Stereo);
489
490            // Audio data should be an AAC sequence header
491            let data = match body {
492                AudioDataBody::Aac(AacPacket::SequenceHeader(data)) => data,
493                _ => panic!("expected aac sequence header"),
494            };
495
496            // The aac sequence header should be able to be decoded into an aac decoder
497            // configuration record
498            let aac_decoder_configuration_record =
499                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
500
501            assert_eq!(
502                aac_decoder_configuration_record.audio_object_type,
503                AudioObjectType::AacLowComplexity
504            );
505            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
506            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
507        }
508
509        // Video Sequence Header Tag
510        {
511            let tag = tags.next().expect("expected tag");
512            assert_eq!(tag.timestamp_ms, 0);
513            assert_eq!(tag.stream_id, 0);
514
515            // This is a video tag
516            let (frame_type, video_data) = match tag.data {
517                FlvTagData::Video(VideoTagHeader { frame_type, body }) => (frame_type, body),
518                _ => panic!("expected video data"),
519            };
520
521            assert_eq!(frame_type, FrameType::Keyframe);
522
523            // Video data should be an AVC sequence header
524            let config = match video_data {
525                VideoTagBody::Enhanced(EnhancedPacket::Av1(Av1Packet::SequenceStart(config))) => config,
526                _ => panic!("expected av1 sequence header found {:?}", video_data),
527            };
528
529            assert_eq!(config.chroma_sample_position, 0);
530            assert!(config.chroma_subsampling_x); // 5.1
531            assert!(config.chroma_subsampling_y);
532            assert!(!config.high_bitdepth);
533            assert!(!config.twelve_bit);
534
535            let mut reader = std::io::Cursor::new(config.config_obu);
536
537            let header = ObuHeader::parse(&mut reader).expect("expected obu header");
538
539            let seq_obu = SequenceHeaderObu::parse(header, &mut reader).expect("expected sequence obu");
540
541            assert_eq!(seq_obu.max_frame_height, 1440);
542            assert_eq!(seq_obu.max_frame_width, 2560);
543        }
544
545        // Rest of the tags should be video / audio data
546        let mut last_timestamp = 0;
547        let mut read_seq_end = false;
548        for tag in tags {
549            assert!(tag.timestamp_ms >= last_timestamp || tag.timestamp_ms == 0); // Timestamps should be monotonically increasing or 0
550            assert_eq!(tag.stream_id, 0);
551
552            if tag.timestamp_ms != 0 {
553                last_timestamp = tag.timestamp_ms;
554            }
555
556            match tag.data {
557                FlvTagData::Audio(AudioData {
558                    body,
559                    sound_rate,
560                    sound_size,
561                    sound_type,
562                }) => {
563                    assert_eq!(sound_rate, SoundRate::Hz44000);
564                    assert_eq!(sound_size, SoundSize::Bit16);
565                    assert_eq!(sound_type, SoundType::Stereo);
566                    match body {
567                        AudioDataBody::Aac(AacPacket::Raw(data)) => data,
568                        _ => panic!("expected aac raw packet"),
569                    };
570                }
571                FlvTagData::Video(VideoTagHeader { frame_type, body }) => {
572                    match frame_type {
573                        FrameType::Keyframe => (),
574                        FrameType::Interframe => (),
575                        _ => panic!("expected keyframe or interframe"),
576                    }
577
578                    match body {
579                        VideoTagBody::Enhanced(EnhancedPacket::Av1(Av1Packet::Raw(_))) => {
580                            assert!(!read_seq_end)
581                        }
582                        VideoTagBody::Enhanced(EnhancedPacket::SequenceEnd { video_codec }) => {
583                            assert!(!read_seq_end);
584                            assert_eq!(video_codec, VideoFourCC::Av1);
585                            read_seq_end = true;
586                        }
587                        _ => panic!("expected av1 raw packet: {:?}", body),
588                    };
589                }
590                _ => panic!("expected audio data"),
591            };
592        }
593
594        assert!(read_seq_end);
595    }
596
597    #[test]
598    fn test_demux_flv_hevc_aac() {
599        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets");
600
601        let data = Bytes::from(std::fs::read(dir.join("hevc_aac.flv")).expect("failed to read file"));
602        let mut reader = io::Cursor::new(data);
603
604        let flv = FlvFile::demux(&mut reader).expect("failed to demux flv");
605
606        assert_eq!(flv.header.version, 1);
607        assert!(flv.header.has_audio);
608        assert!(flv.header.has_video);
609        assert_eq!(flv.header.extra.len(), 0);
610
611        let mut tags = flv.tags.into_iter();
612
613        // Metadata tag
614        {
615            let tag = tags.next().expect("expected tag");
616            assert_eq!(tag.timestamp_ms, 0);
617            assert_eq!(tag.stream_id, 0);
618
619            // This is a metadata tag
620            let script_data = match tag.data {
621                FlvTagData::ScriptData(ScriptData { name, data }) => {
622                    assert_eq!(name, "onMetaData");
623                    data
624                }
625                _ => panic!("expected script data"),
626            };
627
628            // Script data should be an AMF0 object
629            let object = match &script_data[0] {
630                Amf0Value::Object(object) => object,
631                _ => panic!("expected object"),
632            };
633
634            let object = object.iter().map(|(k, v)| (k.as_ref(), v)).collect::<HashMap<_, _>>();
635
636            // Should have a audio sample size property
637            let audio_sample_size = match object.get("audiosamplesize") {
638                Some(Amf0Value::Number(number)) => number,
639                _ => panic!("expected audio sample size"),
640            };
641
642            assert_eq!(audio_sample_size, &16.0);
643
644            // Should have a audio sample rate property
645            let audio_sample_rate = match object.get("audiosamplerate") {
646                Some(Amf0Value::Number(number)) => number,
647                _ => panic!("expected audio sample rate"),
648            };
649
650            assert_eq!(audio_sample_rate, &48000.0);
651
652            // Should have a stereo property
653            let stereo = match object.get("stereo") {
654                Some(Amf0Value::Boolean(boolean)) => boolean,
655                _ => panic!("expected stereo"),
656            };
657
658            assert_eq!(stereo, &true);
659
660            // Should have an audio codec id property
661            let audio_codec_id = match object.get("audiocodecid") {
662                Some(Amf0Value::Number(number)) => number,
663                _ => panic!("expected audio codec id"),
664            };
665
666            assert_eq!(audio_codec_id, &10.0); // AAC
667
668            // Should have a video codec id property
669            let video_codec_id = match object.get("videocodecid") {
670                Some(Amf0Value::Number(number)) => number,
671                _ => panic!("expected video codec id"),
672            };
673
674            assert_eq!(video_codec_id, &7.0); // AVC
675
676            // Should have a duration property
677            let duration = match object.get("duration") {
678                Some(Amf0Value::Number(number)) => number,
679                _ => panic!("expected duration"),
680            };
681
682            assert_eq!(duration, &0.0); // 0 seconds (this was a live stream)
683
684            // Should have a width property
685            let width = match object.get("width") {
686                Some(Amf0Value::Number(number)) => number,
687                _ => panic!("expected width"),
688            };
689
690            assert_eq!(width, &2560.0);
691
692            // Should have a height property
693            let height = match object.get("height") {
694                Some(Amf0Value::Number(number)) => number,
695                _ => panic!("expected height"),
696            };
697
698            assert_eq!(height, &1440.0);
699
700            // Should have a framerate property
701            let framerate = match object.get("framerate") {
702                Some(Amf0Value::Number(number)) => number,
703                _ => panic!("expected framerate"),
704            };
705
706            assert_eq!(framerate, &144.0);
707
708            // Should have a videodatarate property
709            match object.get("videodatarate") {
710                Some(Amf0Value::Number(number)) => number,
711                _ => panic!("expected videodatarate"),
712            };
713
714            // Should have a audiodatarate property
715            match object.get("audiodatarate") {
716                Some(Amf0Value::Number(number)) => number,
717                _ => panic!("expected audiodatarate"),
718            };
719        }
720
721        // Audio Sequence Header Tag
722        {
723            let tag = tags.next().expect("expected tag");
724            assert_eq!(tag.timestamp_ms, 0);
725            assert_eq!(tag.stream_id, 0);
726
727            let (body, sound_rate, sound_size, sound_type) = match tag.data {
728                FlvTagData::Audio(AudioData {
729                    body,
730                    sound_rate,
731                    sound_size,
732                    sound_type,
733                }) => (body, sound_rate, sound_size, sound_type),
734                _ => panic!("expected audio data"),
735            };
736
737            assert_eq!(sound_rate, SoundRate::Hz44000);
738            assert_eq!(sound_size, SoundSize::Bit16);
739            assert_eq!(sound_type, SoundType::Stereo);
740
741            // Audio data should be an AAC sequence header
742            let data = match body {
743                AudioDataBody::Aac(AacPacket::SequenceHeader(data)) => data,
744                _ => panic!("expected aac sequence header"),
745            };
746
747            // The aac sequence header should be able to be decoded into an aac decoder
748            // configuration record
749            let aac_decoder_configuration_record =
750                PartialAudioSpecificConfig::parse(&data).expect("expected aac decoder configuration record");
751
752            assert_eq!(
753                aac_decoder_configuration_record.audio_object_type,
754                AudioObjectType::AacLowComplexity
755            );
756            assert_eq!(aac_decoder_configuration_record.sampling_frequency, 48000);
757            assert_eq!(aac_decoder_configuration_record.channel_configuration, 2);
758        }
759
760        // Video Sequence Header Tag
761        {
762            let tag = tags.next().expect("expected tag");
763            assert_eq!(tag.timestamp_ms, 0);
764            assert_eq!(tag.stream_id, 0);
765
766            // This is a video tag
767            let (frame_type, video_data) = match tag.data {
768                FlvTagData::Video(VideoTagHeader { frame_type, body }) => (frame_type, body),
769                _ => panic!("expected video data"),
770            };
771
772            assert_eq!(frame_type, FrameType::Keyframe);
773
774            // Video data should be an AVC sequence header
775            let config = match video_data {
776                VideoTagBody::Enhanced(EnhancedPacket::Hevc(HevcPacket::SequenceStart(config))) => config,
777                _ => panic!("expected hevc sequence header found {:?}", video_data),
778            };
779
780            assert_eq!(config.configuration_version, 1);
781            assert_eq!(config.avg_frame_rate, 0);
782            assert_eq!(config.constant_frame_rate, 0);
783            assert_eq!(config.num_temporal_layers, 1);
784
785            // We should be able to find a SPS NAL unit in the sequence header
786            let Some(sps) = config
787                .arrays
788                .iter()
789                .find(|a| a.nal_unit_type == scuffle_h265::NaluType::Sps)
790                .and_then(|v| v.nalus.first())
791            else {
792                panic!("expected sps");
793            };
794
795            // We should be able to find a PPS NAL unit in the sequence header
796            let Some(_) = config
797                .arrays
798                .iter()
799                .find(|a| a.nal_unit_type == scuffle_h265::NaluType::Pps)
800                .and_then(|v| v.nalus.first())
801            else {
802                panic!("expected pps");
803            };
804
805            // We should be able to decode the SPS NAL unit
806            let sps = scuffle_h265::Sps::parse(sps.clone()).expect("expected sps");
807
808            assert_eq!(sps.frame_rate, 144.0);
809            assert_eq!(sps.width, 2560);
810            assert_eq!(sps.height, 1440);
811            assert_eq!(
812                sps.color_config,
813                Some(scuffle_h265::ColorConfig {
814                    full_range: false,
815                    color_primaries: 1,
816                    transfer_characteristics: 1,
817                    matrix_coefficients: 1,
818                })
819            )
820        }
821
822        // Rest of the tags should be video / audio data
823        let mut last_timestamp = 0;
824        let mut read_seq_end = false;
825        for tag in tags {
826            assert!(tag.timestamp_ms >= last_timestamp || tag.timestamp_ms == 0); // Timestamps should be monotonically increasing or 0
827            assert_eq!(tag.stream_id, 0);
828
829            if tag.timestamp_ms != 0 {
830                last_timestamp = tag.timestamp_ms;
831            }
832
833            match tag.data {
834                FlvTagData::Audio(AudioData {
835                    body,
836                    sound_rate,
837                    sound_size,
838                    sound_type,
839                }) => {
840                    assert_eq!(sound_rate, SoundRate::Hz44000);
841                    assert_eq!(sound_size, SoundSize::Bit16);
842                    assert_eq!(sound_type, SoundType::Stereo);
843                    match body {
844                        AudioDataBody::Aac(AacPacket::Raw(data)) => data,
845                        _ => panic!("expected aac raw packet"),
846                    };
847                }
848                FlvTagData::Video(VideoTagHeader { frame_type, body }) => {
849                    match frame_type {
850                        FrameType::Keyframe => (),
851                        FrameType::Interframe => (),
852                        _ => panic!("expected keyframe or interframe"),
853                    }
854
855                    match body {
856                        VideoTagBody::Enhanced(EnhancedPacket::Hevc(HevcPacket::Nalu { .. })) => assert!(!read_seq_end),
857                        VideoTagBody::Enhanced(EnhancedPacket::SequenceEnd { video_codec }) => {
858                            assert!(!read_seq_end);
859                            assert_eq!(video_codec, VideoFourCC::Hevc);
860                            read_seq_end = true;
861                        }
862                        _ => panic!("expected hevc nalu packet: {:?}", body),
863                    };
864                }
865                _ => panic!("expected audio data"),
866            };
867        }
868
869        assert!(read_seq_end);
870    }
871}