rtmp_rs/media/
flv.rs

1//! FLV tag parsing
2//!
3//! FLV (Flash Video) is the container format used by RTMP for audio/video data.
4//! Each RTMP audio/video message is essentially an FLV tag without the tag header.
5//!
6//! FLV Tag Structure (for reference, RTMP messages don't include this header):
7//! ```text
8//! +--------+-------------+-----------+
9//! | Type(1)| DataSize(3) | TS(3+1)   | StreamID(3) | Data(N) |
10//! +--------+-------------+-----------+
11//! ```
12//!
13//! RTMP Video Data:
14//! ```text
15//! +----------+----------+
16//! | FrameType| CodecID  | CodecData...
17//! | (4 bits) | (4 bits) |
18//! +----------+----------+
19//! ```
20//!
21//! RTMP Audio Data:
22//! ```text
23//! +----------+----------+----------+----------+
24//! |SoundFormat|SoundRate|SoundSize |SoundType | AudioData...
25//! | (4 bits)  | (2 bits)| (1 bit)  | (1 bit)  |
26//! +----------+----------+----------+----------+
27//! ```
28
29use bytes::Bytes;
30
31/// FLV tag type
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum FlvTagType {
34    Audio,
35    Video,
36    Script,
37}
38
39/// Parsed FLV tag
40#[derive(Debug, Clone)]
41pub struct FlvTag {
42    /// Tag type
43    pub tag_type: FlvTagType,
44    /// Timestamp in milliseconds
45    pub timestamp: u32,
46    /// Raw tag data (including codec headers)
47    pub data: Bytes,
48}
49
50/// Video frame type (upper 4 bits of first byte)
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum VideoFrameType {
53    /// Keyframe (for AVC, a seekable frame)
54    Keyframe = 1,
55    /// Inter frame (for AVC, a non-seekable frame)
56    InterFrame = 2,
57    /// Disposable inter frame (H.263 only)
58    DisposableInterFrame = 3,
59    /// Generated keyframe (reserved for server use)
60    GeneratedKeyframe = 4,
61    /// Video info/command frame
62    VideoInfoFrame = 5,
63}
64
65impl VideoFrameType {
66    pub fn from_byte(b: u8) -> Option<Self> {
67        match (b >> 4) & 0x0F {
68            1 => Some(VideoFrameType::Keyframe),
69            2 => Some(VideoFrameType::InterFrame),
70            3 => Some(VideoFrameType::DisposableInterFrame),
71            4 => Some(VideoFrameType::GeneratedKeyframe),
72            5 => Some(VideoFrameType::VideoInfoFrame),
73            _ => None,
74        }
75    }
76
77    pub fn is_keyframe(&self) -> bool {
78        matches!(
79            self,
80            VideoFrameType::Keyframe | VideoFrameType::GeneratedKeyframe
81        )
82    }
83}
84
85/// Video codec ID (lower 4 bits of first byte)
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum VideoCodec {
88    /// Sorenson H.263
89    SorensonH263 = 2,
90    /// Screen video
91    ScreenVideo = 3,
92    /// VP6
93    Vp6 = 4,
94    /// VP6 with alpha
95    Vp6Alpha = 5,
96    /// Screen video v2
97    ScreenVideoV2 = 6,
98    /// AVC (H.264)
99    Avc = 7,
100    /// HEVC (H.265) - enhanced RTMP extension
101    Hevc = 12,
102    /// AV1 - enhanced RTMP extension
103    Av1 = 13,
104}
105
106impl VideoCodec {
107    pub fn from_byte(b: u8) -> Option<Self> {
108        match b & 0x0F {
109            2 => Some(VideoCodec::SorensonH263),
110            3 => Some(VideoCodec::ScreenVideo),
111            4 => Some(VideoCodec::Vp6),
112            5 => Some(VideoCodec::Vp6Alpha),
113            6 => Some(VideoCodec::ScreenVideoV2),
114            7 => Some(VideoCodec::Avc),
115            12 => Some(VideoCodec::Hevc),
116            13 => Some(VideoCodec::Av1),
117            _ => None,
118        }
119    }
120}
121
122/// Audio format (upper 4 bits of first byte)
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum AudioFormat {
125    /// Linear PCM, platform endian
126    LinearPcmPlatform = 0,
127    /// ADPCM
128    Adpcm = 1,
129    /// MP3
130    Mp3 = 2,
131    /// Linear PCM, little endian
132    LinearPcmLe = 3,
133    /// Nellymoser 16kHz mono
134    Nellymoser16kMono = 4,
135    /// Nellymoser 8kHz mono
136    Nellymoser8kMono = 5,
137    /// Nellymoser
138    Nellymoser = 6,
139    /// G.711 A-law
140    G711ALaw = 7,
141    /// G.711 mu-law
142    G711MuLaw = 8,
143    /// AAC
144    Aac = 10,
145    /// Speex
146    Speex = 11,
147    /// MP3 8kHz
148    Mp38k = 14,
149    /// Device-specific sound
150    DeviceSpecific = 15,
151}
152
153impl AudioFormat {
154    pub fn from_byte(b: u8) -> Option<Self> {
155        match (b >> 4) & 0x0F {
156            0 => Some(AudioFormat::LinearPcmPlatform),
157            1 => Some(AudioFormat::Adpcm),
158            2 => Some(AudioFormat::Mp3),
159            3 => Some(AudioFormat::LinearPcmLe),
160            4 => Some(AudioFormat::Nellymoser16kMono),
161            5 => Some(AudioFormat::Nellymoser8kMono),
162            6 => Some(AudioFormat::Nellymoser),
163            7 => Some(AudioFormat::G711ALaw),
164            8 => Some(AudioFormat::G711MuLaw),
165            10 => Some(AudioFormat::Aac),
166            11 => Some(AudioFormat::Speex),
167            14 => Some(AudioFormat::Mp38k),
168            15 => Some(AudioFormat::DeviceSpecific),
169            _ => None,
170        }
171    }
172}
173
174/// Audio sample rate
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum AudioSampleRate {
177    Rate5512 = 0,
178    Rate11025 = 1,
179    Rate22050 = 2,
180    Rate44100 = 3,
181}
182
183impl AudioSampleRate {
184    pub fn from_byte(b: u8) -> Self {
185        match (b >> 2) & 0x03 {
186            0 => AudioSampleRate::Rate5512,
187            1 => AudioSampleRate::Rate11025,
188            2 => AudioSampleRate::Rate22050,
189            _ => AudioSampleRate::Rate44100,
190        }
191    }
192
193    pub fn to_hz(&self) -> u32 {
194        match self {
195            AudioSampleRate::Rate5512 => 5512,
196            AudioSampleRate::Rate11025 => 11025,
197            AudioSampleRate::Rate22050 => 22050,
198            AudioSampleRate::Rate44100 => 44100,
199        }
200    }
201}
202
203impl FlvTag {
204    /// Create a new video tag
205    pub fn video(timestamp: u32, data: Bytes) -> Self {
206        Self {
207            tag_type: FlvTagType::Video,
208            timestamp,
209            data,
210        }
211    }
212
213    /// Create a new audio tag
214    pub fn audio(timestamp: u32, data: Bytes) -> Self {
215        Self {
216            tag_type: FlvTagType::Audio,
217            timestamp,
218            data,
219        }
220    }
221
222    /// Check if this is a video tag
223    pub fn is_video(&self) -> bool {
224        self.tag_type == FlvTagType::Video
225    }
226
227    /// Check if this is an audio tag
228    pub fn is_audio(&self) -> bool {
229        self.tag_type == FlvTagType::Audio
230    }
231
232    /// For video tags, get the frame type
233    pub fn video_frame_type(&self) -> Option<VideoFrameType> {
234        if self.is_video() && !self.data.is_empty() {
235            VideoFrameType::from_byte(self.data[0])
236        } else {
237            None
238        }
239    }
240
241    /// For video tags, get the codec
242    pub fn video_codec(&self) -> Option<VideoCodec> {
243        if self.is_video() && !self.data.is_empty() {
244            VideoCodec::from_byte(self.data[0])
245        } else {
246            None
247        }
248    }
249
250    /// For audio tags, get the format
251    pub fn audio_format(&self) -> Option<AudioFormat> {
252        if self.is_audio() && !self.data.is_empty() {
253            AudioFormat::from_byte(self.data[0])
254        } else {
255            None
256        }
257    }
258
259    /// Check if this is a keyframe
260    pub fn is_keyframe(&self) -> bool {
261        self.video_frame_type()
262            .map(|ft| ft.is_keyframe())
263            .unwrap_or(false)
264    }
265
266    /// Check if this is an AVC sequence header
267    pub fn is_avc_sequence_header(&self) -> bool {
268        if self.is_video() && self.data.len() >= 2 {
269            let codec = VideoCodec::from_byte(self.data[0]);
270            codec == Some(VideoCodec::Avc) && self.data[1] == 0
271        } else {
272            false
273        }
274    }
275
276    /// Check if this is an AAC sequence header
277    pub fn is_aac_sequence_header(&self) -> bool {
278        if self.is_audio() && self.data.len() >= 2 {
279            let format = AudioFormat::from_byte(self.data[0]);
280            format == Some(AudioFormat::Aac) && self.data[1] == 0
281        } else {
282            false
283        }
284    }
285
286    /// Get the size of the tag data
287    pub fn size(&self) -> usize {
288        self.data.len()
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_video_frame_type() {
298        // Keyframe + AVC
299        assert_eq!(
300            VideoFrameType::from_byte(0x17),
301            Some(VideoFrameType::Keyframe)
302        );
303        assert_eq!(VideoCodec::from_byte(0x17), Some(VideoCodec::Avc));
304
305        // Inter frame + AVC
306        assert_eq!(
307            VideoFrameType::from_byte(0x27),
308            Some(VideoFrameType::InterFrame)
309        );
310    }
311
312    #[test]
313    fn test_avc_sequence_header() {
314        let header = FlvTag::video(0, Bytes::from_static(&[0x17, 0x00, 0x00, 0x00, 0x00]));
315        assert!(header.is_avc_sequence_header());
316        assert!(header.is_keyframe());
317
318        let frame = FlvTag::video(0, Bytes::from_static(&[0x17, 0x01, 0x00, 0x00, 0x00]));
319        assert!(!frame.is_avc_sequence_header());
320    }
321
322    #[test]
323    fn test_aac_sequence_header() {
324        let header = FlvTag::audio(0, Bytes::from_static(&[0xAF, 0x00, 0x12, 0x10]));
325        assert!(header.is_aac_sequence_header());
326
327        let frame = FlvTag::audio(0, Bytes::from_static(&[0xAF, 0x01, 0x21, 0x00]));
328        assert!(!frame.is_aac_sequence_header());
329    }
330}