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