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