scuffle_flv/video/
mod.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
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, // enhanced + keyframe + metadata
167            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(), // empty object
207            }
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, // legacy + keyframe + avc packet
215            0x01,        // nalu
216            0x02,        // composition time
217            0x03,
218            0x04,
219            0x05, // data
220            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, // legacy + keyframe + avc packet
246            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, // enhanced + keyframe + coded frames x
281            b'h',        // video codec
282            b'v',
283            b'c',
284            b'1',
285            0x01, // data
286            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, // enhanced + keyframe + coded frames x
321            b'h',        // video codec
322            b'v',
323            b'c',
324            b'1',
325            0x01, // data
326            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, // enhanced + keyframe + coded frames
364            b'a',        // video codec
365            b'v',
366            b'0',
367            b'1',
368            0x01, // data
369            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, // legacy + command
406            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, // enhanced + keyframe + SequenceEnd
425            b'a',
426            b'v',
427            b'0',
428            b'1', // video codec
429        ]));
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, // legacy + keyframe + SorensonH263
458            0,           // data
459            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, // enhanced + keyframe + sequence start
487            b'a',        // video codec
488            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}