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