scuffle_flv/
tag.rs

1//! FLV Tag processing
2
3use byteorder::{BigEndian, ReadBytesExt};
4use bytes::Bytes;
5use nutype_enum::nutype_enum;
6use scuffle_bytes_util::BytesCursorExt;
7
8use super::audio::AudioData;
9use super::script::ScriptData;
10use super::video::VideoData;
11use crate::error::FlvError;
12
13/// An FLV Tag
14///
15/// Tags have different types and thus different data structures. To accommodate
16/// this the [`FlvTagData`] enum is used.
17///
18/// Defined by:
19/// - Legacy FLV spec, Annex E.4.1
20///
21/// The v10.1 spec adds some additional fields to the tag to accomodate
22/// encryption. We dont support this because it is not needed for our use case.
23/// (and I suspect it is not used anywhere anymore.)
24#[derive(Debug, Clone, PartialEq)]
25pub struct FlvTag<'a> {
26    /// The timestamp of this tag in milliseconds
27    pub timestamp_ms: u32,
28    /// The stream id of this tag
29    pub stream_id: u32,
30    /// The actual data of the tag
31    pub data: FlvTagData<'a>,
32}
33
34impl FlvTag<'_> {
35    /// Demux a FLV tag from the given reader.
36    ///
37    /// The reader will be advanced to the end of the tag.
38    ///
39    /// The reader needs to be a [`std::io::Cursor`] with a [`Bytes`] buffer because we
40    /// take advantage of zero-copy reading.
41    pub fn demux(reader: &mut std::io::Cursor<Bytes>) -> Result<Self, FlvError> {
42        let first_byte = reader.read_u8()?;
43
44        // encrypted
45        let filter = (first_byte & 0b0010_0000) != 0;
46
47        // Only the last 5 bits are the tag type.
48        let tag_type = FlvTagType::from(first_byte & 0b00011111);
49
50        let data_size = reader.read_u24::<BigEndian>()?;
51        // The timestamp bit is weird. Its 24bits but then there is an extended 8 bit
52        // number to create a 32bit number.
53        let timestamp_ms = reader.read_u24::<BigEndian>()? | ((reader.read_u8()? as u32) << 24);
54
55        // The stream id according to the spec is ALWAYS 0. (likely not true)
56        let stream_id = reader.read_u24::<BigEndian>()?;
57
58        // We then extract the data from the reader. (advancing the cursor to the end of
59        // the tag)
60        let data = reader.extract_bytes(data_size as usize)?;
61
62        let data = if !filter {
63            // Finally we demux the data.
64            FlvTagData::demux(tag_type, &mut std::io::Cursor::new(data))?
65        } else {
66            // If the tag is encrypted we just return the data as is.
67            FlvTagData::Encrypted { data }
68        };
69
70        Ok(FlvTag {
71            timestamp_ms,
72            stream_id,
73            data,
74        })
75    }
76}
77
78nutype_enum! {
79    /// FLV Tag Type
80    ///
81    /// This is the type of the tag.
82    ///
83    /// Defined by:
84    /// - video_file_format_spec_v10.pdf (Chapter 1 - The FLV File Format - FLV tags)
85    /// - video_file_format_spec_v10_1.pdf (Annex E.4.1 - FLV Tag)
86    ///
87    /// The 3 types that are supported are:
88    /// - Audio(8)
89    /// - Video(9)
90    /// - ScriptData(18)
91    pub enum FlvTagType(u8) {
92        /// [`AudioData`]
93        Audio = 8,
94        /// [`VideoData`]
95        Video = 9,
96        /// [`ScriptData`]
97        ScriptData = 18,
98    }
99}
100
101/// FLV Tag Data
102///
103/// This is a container for the actual media data.
104/// This enum contains the data for the different types of tags.
105///
106/// Defined by:
107/// - Legacy FLV spec, Annex E.4.1
108#[derive(Debug, Clone, PartialEq)]
109pub enum FlvTagData<'a> {
110    /// AudioData when the FlvTagType is Audio(8)
111    ///
112    /// Defined by:
113    /// - Legacy FLV spec, Annex E.4.2.1
114    Audio(AudioData),
115    /// VideoData when the FlvTagType is Video(9)
116    ///
117    /// Defined by:
118    /// - Legacy FLV spec, Annex E.4.3.1
119    Video(VideoData<'a>),
120    /// ScriptData when the FlvTagType is ScriptData(18)
121    ///
122    /// Defined by:
123    /// - Legacy FLV spec, Annex E.4.4.1
124    ScriptData(ScriptData<'a>),
125    /// Encrypted tag.
126    ///
127    /// This library neither supports demuxing nor decrypting encrypted tags.
128    Encrypted {
129        /// The raw unencrypted tag data.
130        ///
131        /// This includes all data that follows the StreamID field.
132        /// See the legacy FLV spec, Annex E.4.1 for more information.
133        data: Bytes,
134    },
135    /// Any tag type that we dont know how to demux, with the corresponding data
136    /// being the raw bytes of the tag.
137    Unknown {
138        /// The tag type.
139        tag_type: FlvTagType,
140        /// The raw data of the tag.
141        data: Bytes,
142    },
143}
144
145impl FlvTagData<'_> {
146    /// Demux a FLV tag data from the given reader.
147    ///
148    /// The reader will be enirely consumed.
149    ///
150    /// The reader needs to be a [`std::io::Cursor`] with a [`Bytes`] buffer because we
151    /// take advantage of zero-copy reading.
152    pub fn demux(tag_type: FlvTagType, reader: &mut std::io::Cursor<Bytes>) -> Result<Self, FlvError> {
153        match tag_type {
154            FlvTagType::Audio => Ok(FlvTagData::Audio(AudioData::demux(reader)?)),
155            FlvTagType::Video => Ok(FlvTagData::Video(VideoData::demux(reader)?)),
156            FlvTagType::ScriptData => Ok(FlvTagData::ScriptData(ScriptData::demux(reader)?)),
157            _ => Ok(FlvTagData::Unknown {
158                tag_type,
159                data: reader.extract_remaining(),
160            }),
161        }
162    }
163}