1use std::io;
6
7use body::VideoTagBody;
8use bytes::Bytes;
9use header::VideoTagHeader;
10
11use crate::error::FlvError;
12
13pub mod body;
14pub mod header;
15
16#[derive(Debug, Clone, PartialEq)]
24pub struct VideoData<'a> {
25 pub header: VideoTagHeader,
27 pub body: VideoTagBody<'a>,
29}
30
31impl VideoData<'_> {
32 #[allow(clippy::unusual_byte_groupings)]
39 pub fn demux(reader: &mut io::Cursor<Bytes>) -> Result<Self, FlvError> {
40 let header = VideoTagHeader::demux(reader)?;
41 let body = VideoTagBody::demux(&header, reader)?;
42
43 Ok(VideoData { header, body })
44 }
45}
46
47#[cfg(test)]
48#[cfg_attr(all(test, coverage_nightly), coverage(off))]
49mod tests {
50 use scuffle_amf0::{Amf0Marker, Amf0Object};
51 use scuffle_av1::AV1CodecConfigurationRecord;
52 use scuffle_bytes_util::BytesCow;
53
54 use super::header::enhanced::{VideoFourCc, VideoPacketType};
55 use super::header::legacy::VideoCodecId;
56 use super::header::{VideoCommand, VideoFrameType};
57 use super::*;
58 use crate::video::body::enhanced::metadata::VideoPacketMetadataEntry;
59 use crate::video::body::enhanced::{ExVideoTagBody, VideoPacket, VideoPacketCodedFrames, VideoPacketSequenceStart};
60 use crate::video::body::legacy::LegacyVideoTagBody;
61 use crate::video::header::VideoTagHeaderData;
62 use crate::video::header::enhanced::{ExVideoTagHeader, ExVideoTagHeaderContent};
63 use crate::video::header::legacy::{AvcPacketType, LegacyVideoTagHeader, LegacyVideoTagHeaderAvcPacket};
64
65 #[test]
66 fn test_video_fourcc() {
67 let cases = [
68 (VideoFourCc::Av1, *b"av01", "VideoFourCc::Av1"),
69 (VideoFourCc::Vp9, *b"vp09", "VideoFourCc::Vp9"),
70 (VideoFourCc::Hevc, *b"hvc1", "VideoFourCc::Hevc"),
71 (VideoFourCc(*b"av02"), *b"av02", "VideoFourCc([97, 118, 48, 50])"),
72 ];
73
74 for (expected, bytes, name) in cases {
75 assert_eq!(VideoFourCc::from(bytes), expected);
76 assert_eq!(format!("{:?}", VideoFourCc::from(bytes)), name);
77 }
78 }
79
80 #[test]
81 fn test_enhanced_packet_type() {
82 let cases = [
83 (VideoPacketType::SequenceStart, 0, "VideoPacketType::SequenceStart"),
84 (VideoPacketType::CodedFrames, 1, "VideoPacketType::CodedFrames"),
85 (VideoPacketType::SequenceEnd, 2, "VideoPacketType::SequenceEnd"),
86 (VideoPacketType::CodedFramesX, 3, "VideoPacketType::CodedFramesX"),
87 (VideoPacketType::Metadata, 4, "VideoPacketType::Metadata"),
88 (
89 VideoPacketType::Mpeg2TsSequenceStart,
90 5,
91 "VideoPacketType::Mpeg2TsSequenceStart",
92 ),
93 (VideoPacketType::Multitrack, 6, "VideoPacketType::Multitrack"),
94 (VideoPacketType::ModEx, 7, "VideoPacketType::ModEx"),
95 ];
96
97 for (expected, value, name) in cases {
98 assert_eq!(VideoPacketType::from(value), expected);
99 assert_eq!(format!("{:?}", VideoPacketType::from(value)), name);
100 }
101 }
102
103 #[test]
104 fn test_frame_type() {
105 let cases = [
106 (VideoFrameType::KeyFrame, 1, "VideoFrameType::KeyFrame"),
107 (VideoFrameType::InterFrame, 2, "VideoFrameType::InterFrame"),
108 (
109 VideoFrameType::DisposableInterFrame,
110 3,
111 "VideoFrameType::DisposableInterFrame",
112 ),
113 (VideoFrameType::GeneratedKeyFrame, 4, "VideoFrameType::GeneratedKeyFrame"),
114 (VideoFrameType::Command, 5, "VideoFrameType::Command"),
115 (VideoFrameType(6), 6, "VideoFrameType(6)"),
116 (VideoFrameType(7), 7, "VideoFrameType(7)"),
117 ];
118
119 for (expected, value, name) in cases {
120 assert_eq!(VideoFrameType::from(value), expected);
121 assert_eq!(format!("{:?}", VideoFrameType::from(value)), name);
122 }
123 }
124
125 #[test]
126 fn test_video_codec_id() {
127 let cases = [
128 (VideoCodecId::SorensonH263, 2, "VideoCodecId::SorensonH263"),
129 (VideoCodecId::ScreenVideo, 3, "VideoCodecId::ScreenVideo"),
130 (VideoCodecId::On2VP6, 4, "VideoCodecId::On2VP6"),
131 (
132 VideoCodecId::On2VP6WithAlphaChannel,
133 5,
134 "VideoCodecId::On2VP6WithAlphaChannel",
135 ),
136 (VideoCodecId::ScreenVideoVersion2, 6, "VideoCodecId::ScreenVideoVersion2"),
137 (VideoCodecId::Avc, 7, "VideoCodecId::Avc"),
138 (VideoCodecId(10), 10, "VideoCodecId(10)"),
139 (VideoCodecId(11), 11, "VideoCodecId(11)"),
140 (VideoCodecId(15), 15, "VideoCodecId(15)"),
141 ];
142
143 for (expected, value, name) in cases {
144 assert_eq!(VideoCodecId::from(value), expected);
145 assert_eq!(format!("{:?}", VideoCodecId::from(value)), name);
146 }
147 }
148
149 #[test]
150 fn test_command_packet() {
151 let cases = [
152 (VideoCommand::StartSeek, 0, "VideoCommand::StartSeek"),
153 (VideoCommand::EndSeek, 1, "VideoCommand::EndSeek"),
154 (VideoCommand(3), 3, "VideoCommand(3)"),
155 (VideoCommand(4), 4, "VideoCommand(4)"),
156 ];
157
158 for (expected, value, name) in cases {
159 assert_eq!(VideoCommand::from(value), expected);
160 assert_eq!(format!("{:?}", VideoCommand::from(value)), name);
161 }
162 }
163
164 #[test]
165 fn test_video_data_body_metadata() {
166 let mut reader = io::Cursor::new(Bytes::from_static(&[
167 0b1001_0100, 1,
169 2,
170 3,
171 4,
172 Amf0Marker::String as u8,
173 0,
174 0,
175 Amf0Marker::Object as u8,
176 0,
177 0,
178 Amf0Marker::ObjectEnd as u8,
179 ]));
180 let video = VideoData::demux(&mut reader).unwrap();
181
182 assert_eq!(
183 video.header,
184 VideoTagHeader {
185 frame_type: VideoFrameType::KeyFrame,
186 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
187 video_packet_type: VideoPacketType::Metadata,
188 video_packet_mod_exs: vec![],
189 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc([1, 2, 3, 4]))
190 })
191 }
192 );
193
194 let VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
195 video_four_cc: VideoFourCc([1, 2, 3, 4]),
196 packet: VideoPacket::Metadata(metadata),
197 }) = video.body
198 else {
199 panic!("unexpected body: {:?}", video.body);
200 };
201
202 assert_eq!(metadata.len(), 1);
203 assert_eq!(
204 metadata[0],
205 VideoPacketMetadataEntry::Other {
206 key: "".into(),
207 object: Amf0Object::new(), }
209 );
210 }
211
212 #[test]
213 fn test_video_data_body_avc() {
214 let mut reader = io::Cursor::new(Bytes::from_static(&[
215 0b0001_0111, 0x01, 0x02, 0x03,
219 0x04,
220 0x05, 0x06,
222 0x07,
223 0x08,
224 ]));
225
226 let video = VideoData::demux(&mut reader).unwrap();
227
228 assert_eq!(
229 video.header,
230 VideoTagHeader {
231 frame_type: VideoFrameType::KeyFrame,
232 data: VideoTagHeaderData::Legacy(LegacyVideoTagHeader::AvcPacket(LegacyVideoTagHeaderAvcPacket::Nalu {
233 composition_time_offset: 0x020304
234 }))
235 }
236 );
237
238 assert_eq!(
239 video.body,
240 VideoTagBody::Legacy(LegacyVideoTagBody::Other {
241 data: Bytes::from_static(&[0x05, 0x06, 0x07, 0x08])
242 })
243 );
244
245 let mut reader = io::Cursor::new(Bytes::from_static(&[
246 0b0001_0111, 0x05,
248 0x02,
249 0x03,
250 0x04,
251 0x05,
252 0x06,
253 0x07,
254 0x08,
255 ]));
256
257 let video = VideoData::demux(&mut reader).unwrap();
258
259 assert_eq!(
260 video.header,
261 VideoTagHeader {
262 frame_type: VideoFrameType::KeyFrame,
263 data: VideoTagHeaderData::Legacy(LegacyVideoTagHeader::AvcPacket(LegacyVideoTagHeaderAvcPacket::Unknown {
264 avc_packet_type: AvcPacketType(0x05),
265 composition_time_offset: 0x020304
266 })),
267 }
268 );
269
270 assert_eq!(
271 video.body,
272 VideoTagBody::Legacy(LegacyVideoTagBody::Other {
273 data: Bytes::from_static(&[0x05, 0x06, 0x07, 0x08])
274 })
275 );
276 }
277
278 #[test]
279 fn test_video_data_body_hevc() {
280 let mut reader = io::Cursor::new(Bytes::from_static(&[
281 0b1001_0011, b'h', b'v',
284 b'c',
285 b'1',
286 0x01, 0x02,
288 0x03,
289 0x04,
290 0x05,
291 0x06,
292 0x07,
293 0x08,
294 ]));
295
296 let video = VideoData::demux(&mut reader).unwrap();
297
298 assert_eq!(
299 video.header,
300 VideoTagHeader {
301 frame_type: VideoFrameType::KeyFrame,
302 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
303 video_packet_type: VideoPacketType::CodedFramesX,
304 video_packet_mod_exs: vec![],
305 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc([b'h', b'v', b'c', b'1'])),
306 })
307 }
308 );
309
310 assert_eq!(
311 video.body,
312 VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
313 video_four_cc: VideoFourCc([b'h', b'v', b'c', b'1']),
314 packet: VideoPacket::CodedFramesX {
315 data: Bytes::from_static(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
316 },
317 })
318 );
319
320 let mut reader = io::Cursor::new(Bytes::from_static(&[
321 0b1001_0011, b'h', b'v',
324 b'c',
325 b'1',
326 0x01, 0x02,
328 0x03,
329 0x04,
330 0x05,
331 0x06,
332 0x07,
333 0x08,
334 ]));
335
336 let video = VideoData::demux(&mut reader).unwrap();
337
338 assert_eq!(
339 video.header,
340 VideoTagHeader {
341 frame_type: VideoFrameType::KeyFrame,
342 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
343 video_packet_type: VideoPacketType::CodedFramesX,
344 video_packet_mod_exs: vec![],
345 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc::Hevc),
346 })
347 }
348 );
349
350 assert_eq!(
351 video.body,
352 VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
353 video_four_cc: VideoFourCc::Hevc,
354 packet: VideoPacket::CodedFramesX {
355 data: Bytes::from_static(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
356 },
357 })
358 );
359 }
360
361 #[test]
362 fn test_video_data_body_av1() {
363 let mut reader = io::Cursor::new(Bytes::from_static(&[
364 0b1001_0001, b'a', b'v',
367 b'0',
368 b'1',
369 0x01, 0x02,
371 0x03,
372 0x04,
373 0x05,
374 0x06,
375 0x07,
376 0x08,
377 ]));
378
379 let video = VideoData::demux(&mut reader).unwrap();
380
381 assert_eq!(
382 video.header,
383 VideoTagHeader {
384 frame_type: VideoFrameType::KeyFrame,
385 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
386 video_packet_type: VideoPacketType::CodedFrames,
387 video_packet_mod_exs: vec![],
388 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc::Av1)
389 })
390 }
391 );
392 assert_eq!(
393 video.body,
394 VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
395 video_four_cc: VideoFourCc::Av1,
396 packet: VideoPacket::CodedFrames(VideoPacketCodedFrames::Other(Bytes::from_static(&[
397 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
398 ])))
399 })
400 );
401 }
402
403 #[test]
404 fn test_video_data_command_packet() {
405 let mut reader = io::Cursor::new(Bytes::from_static(&[
406 0b0101_0000, 0x00,
408 ]));
409
410 let video = VideoData::demux(&mut reader).unwrap();
411
412 assert_eq!(
413 video.header,
414 VideoTagHeader {
415 frame_type: VideoFrameType::Command,
416 data: VideoTagHeaderData::Legacy(LegacyVideoTagHeader::VideoCommand(VideoCommand::StartSeek))
417 }
418 );
419 assert_eq!(video.body, VideoTagBody::Legacy(LegacyVideoTagBody::Command));
420 }
421
422 #[test]
423 fn test_video_data_demux_enhanced() {
424 let mut reader = io::Cursor::new(Bytes::from_static(&[
425 0b1001_0010, b'a',
427 b'v',
428 b'0',
429 b'1', ]));
431
432 let video = VideoData::demux(&mut reader).unwrap();
433
434 assert_eq!(
435 video.header,
436 VideoTagHeader {
437 frame_type: VideoFrameType::KeyFrame,
438 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
439 video_packet_type: VideoPacketType::SequenceEnd,
440 video_packet_mod_exs: vec![],
441 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc::Av1)
442 })
443 }
444 );
445
446 assert_eq!(
447 video.body,
448 VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
449 video_four_cc: VideoFourCc::Av1,
450 packet: VideoPacket::SequenceEnd
451 })
452 );
453 }
454
455 #[test]
456 fn test_video_data_demux_h263() {
457 let mut reader = io::Cursor::new(Bytes::from_static(&[
458 0b0001_0010, 0, 1,
461 2,
462 3,
463 ]));
464
465 let video = VideoData::demux(&mut reader).unwrap();
466
467 assert_eq!(
468 video.header,
469 VideoTagHeader {
470 frame_type: VideoFrameType::KeyFrame,
471 data: VideoTagHeaderData::Legacy(LegacyVideoTagHeader::Other {
472 video_codec_id: VideoCodecId::SorensonH263
473 })
474 }
475 );
476 assert_eq!(
477 video.body,
478 VideoTagBody::Legacy(LegacyVideoTagBody::Other {
479 data: Bytes::from_static(&[0, 1, 2, 3])
480 })
481 );
482 }
483
484 #[test]
485 fn test_av1_sequence_start() {
486 let mut reader = io::Cursor::new(Bytes::from_static(&[
487 0b1001_0000, b'a', b'v',
490 b'0',
491 b'1',
492 129,
493 13,
494 12,
495 0,
496 10,
497 15,
498 0,
499 0,
500 0,
501 106,
502 239,
503 191,
504 225,
505 188,
506 2,
507 25,
508 144,
509 16,
510 16,
511 16,
512 64,
513 ]));
514
515 let video = VideoData::demux(&mut reader).unwrap();
516
517 assert_eq!(
518 video.header,
519 VideoTagHeader {
520 frame_type: VideoFrameType::KeyFrame,
521 data: VideoTagHeaderData::Enhanced(ExVideoTagHeader {
522 video_packet_type: VideoPacketType::SequenceStart,
523 video_packet_mod_exs: vec![],
524 content: ExVideoTagHeaderContent::NoMultiTrack(VideoFourCc::Av1),
525 })
526 }
527 );
528
529 assert_eq!(
530 video.body,
531 VideoTagBody::Enhanced(ExVideoTagBody::NoMultitrack {
532 video_four_cc: VideoFourCc::Av1,
533 packet: VideoPacket::SequenceStart(VideoPacketSequenceStart::Av1(AV1CodecConfigurationRecord {
534 seq_profile: 0,
535 seq_level_idx_0: 13,
536 seq_tier_0: false,
537 high_bitdepth: false,
538 twelve_bit: false,
539 monochrome: false,
540 chroma_subsampling_x: true,
541 chroma_subsampling_y: true,
542 chroma_sample_position: 0,
543 hdr_wcg_idc: 0,
544 initial_presentation_delay_minus_one: None,
545 config_obu: BytesCow::from_static(b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@"),
546 }))
547 }),
548 );
549 }
550}