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