scuffle_flv/
video.rs

1//! FLV video processing
2//!
3//! Use [`VideoData`] to demux video data contained in an RTMP video message.
4
5use 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/// FLV `VIDEODATA` tag
17///
18/// This is a container for legacy as well as enhanced video data.
19///
20/// Defined by:
21/// - Legacy FLV spec, Annex E.4.3.1
22/// - Enhanced RTMP spec, page 26-31, Enhanced Video
23#[derive(Debug, Clone, PartialEq)]
24pub struct VideoData<'a> {
25    /// The header of the video data.
26    pub header: VideoTagHeader,
27    /// The body of the video data.
28    pub body: VideoTagBody<'a>,
29}
30
31impl VideoData<'_> {
32    /// Demux video data from a given reader.
33    ///
34    /// This function will automatically determine whether the given data represents a legacy or enhanced video data
35    /// and demux it accordingly.
36    ///
37    /// Returns a new instance of [`VideoData`] if successful.
38    #[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, // enhanced + keyframe + metadata
168            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(), // empty object
208            }
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, // legacy + keyframe + avc packet
216            0x01,        // nalu
217            0x02,        // composition time
218            0x03,
219            0x04,
220            0x05, // data
221            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, // legacy + keyframe + avc packet
247            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, // enhanced + keyframe + coded frames x
282            b'h',        // video codec
283            b'v',
284            b'c',
285            b'1',
286            0x01, // data
287            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, // enhanced + keyframe + coded frames x
322            b'h',        // video codec
323            b'v',
324            b'c',
325            b'1',
326            0x01, // data
327            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, // enhanced + keyframe + coded frames
365            b'a',        // video codec
366            b'v',
367            b'0',
368            b'1',
369            0x01, // data
370            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, // legacy + command
407            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, // enhanced + keyframe + SequenceEnd
426            b'a',
427            b'v',
428            b'0',
429            b'1', // video codec
430        ]));
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, // legacy + keyframe + SorensonH263
459            0,           // data
460            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, // enhanced + keyframe + sequence start
488            b'a',        // video codec
489            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}