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
331    #[test]
332    fn test_video_frame_type_all_values() {
333        assert_eq!(
334            VideoFrameType::from_byte(0x10),
335            Some(VideoFrameType::Keyframe)
336        );
337        assert_eq!(
338            VideoFrameType::from_byte(0x20),
339            Some(VideoFrameType::InterFrame)
340        );
341        assert_eq!(
342            VideoFrameType::from_byte(0x30),
343            Some(VideoFrameType::DisposableInterFrame)
344        );
345        assert_eq!(
346            VideoFrameType::from_byte(0x40),
347            Some(VideoFrameType::GeneratedKeyframe)
348        );
349        assert_eq!(
350            VideoFrameType::from_byte(0x50),
351            Some(VideoFrameType::VideoInfoFrame)
352        );
353        assert_eq!(VideoFrameType::from_byte(0x00), None);
354        assert_eq!(VideoFrameType::from_byte(0x60), None);
355    }
356
357    #[test]
358    fn test_video_frame_type_is_keyframe() {
359        assert!(VideoFrameType::Keyframe.is_keyframe());
360        assert!(VideoFrameType::GeneratedKeyframe.is_keyframe());
361        assert!(!VideoFrameType::InterFrame.is_keyframe());
362        assert!(!VideoFrameType::DisposableInterFrame.is_keyframe());
363        assert!(!VideoFrameType::VideoInfoFrame.is_keyframe());
364    }
365
366    #[test]
367    fn test_video_codec_all_values() {
368        assert_eq!(VideoCodec::from_byte(0x02), Some(VideoCodec::SorensonH263));
369        assert_eq!(VideoCodec::from_byte(0x03), Some(VideoCodec::ScreenVideo));
370        assert_eq!(VideoCodec::from_byte(0x04), Some(VideoCodec::Vp6));
371        assert_eq!(VideoCodec::from_byte(0x05), Some(VideoCodec::Vp6Alpha));
372        assert_eq!(VideoCodec::from_byte(0x06), Some(VideoCodec::ScreenVideoV2));
373        assert_eq!(VideoCodec::from_byte(0x07), Some(VideoCodec::Avc));
374        assert_eq!(VideoCodec::from_byte(0x0C), Some(VideoCodec::Hevc));
375        assert_eq!(VideoCodec::from_byte(0x0D), Some(VideoCodec::Av1));
376        assert_eq!(VideoCodec::from_byte(0x00), None);
377        assert_eq!(VideoCodec::from_byte(0x01), None);
378        assert_eq!(VideoCodec::from_byte(0x08), None);
379    }
380
381    #[test]
382    fn test_audio_format_all_values() {
383        assert_eq!(
384            AudioFormat::from_byte(0x00),
385            Some(AudioFormat::LinearPcmPlatform)
386        );
387        assert_eq!(AudioFormat::from_byte(0x10), Some(AudioFormat::Adpcm));
388        assert_eq!(AudioFormat::from_byte(0x20), Some(AudioFormat::Mp3));
389        assert_eq!(AudioFormat::from_byte(0x30), Some(AudioFormat::LinearPcmLe));
390        assert_eq!(
391            AudioFormat::from_byte(0x40),
392            Some(AudioFormat::Nellymoser16kMono)
393        );
394        assert_eq!(
395            AudioFormat::from_byte(0x50),
396            Some(AudioFormat::Nellymoser8kMono)
397        );
398        assert_eq!(AudioFormat::from_byte(0x60), Some(AudioFormat::Nellymoser));
399        assert_eq!(AudioFormat::from_byte(0x70), Some(AudioFormat::G711ALaw));
400        assert_eq!(AudioFormat::from_byte(0x80), Some(AudioFormat::G711MuLaw));
401        assert_eq!(AudioFormat::from_byte(0xA0), Some(AudioFormat::Aac));
402        assert_eq!(AudioFormat::from_byte(0xB0), Some(AudioFormat::Speex));
403        assert_eq!(AudioFormat::from_byte(0xE0), Some(AudioFormat::Mp38k));
404        assert_eq!(
405            AudioFormat::from_byte(0xF0),
406            Some(AudioFormat::DeviceSpecific)
407        );
408        assert_eq!(AudioFormat::from_byte(0x90), None); // 9 is not defined
409    }
410
411    #[test]
412    fn test_audio_sample_rate() {
413        assert_eq!(AudioSampleRate::from_byte(0x00).to_hz(), 5512);
414        assert_eq!(AudioSampleRate::from_byte(0x04).to_hz(), 11025);
415        assert_eq!(AudioSampleRate::from_byte(0x08).to_hz(), 22050);
416        assert_eq!(AudioSampleRate::from_byte(0x0C).to_hz(), 44100);
417        // Test masking
418        assert_eq!(AudioSampleRate::from_byte(0xFF).to_hz(), 44100);
419    }
420
421    #[test]
422    fn test_flv_tag_video_construction() {
423        let tag = FlvTag::video(1000, Bytes::from_static(&[0x17, 0x01]));
424        assert!(tag.is_video());
425        assert!(!tag.is_audio());
426        assert_eq!(tag.tag_type, FlvTagType::Video);
427        assert_eq!(tag.timestamp, 1000);
428    }
429
430    #[test]
431    fn test_flv_tag_audio_construction() {
432        let tag = FlvTag::audio(2000, Bytes::from_static(&[0xAF, 0x01]));
433        assert!(tag.is_audio());
434        assert!(!tag.is_video());
435        assert_eq!(tag.tag_type, FlvTagType::Audio);
436        assert_eq!(tag.timestamp, 2000);
437    }
438
439    #[test]
440    fn test_flv_tag_video_frame_type() {
441        // Keyframe AVC
442        let keyframe = FlvTag::video(0, Bytes::from_static(&[0x17, 0x01]));
443        assert_eq!(keyframe.video_frame_type(), Some(VideoFrameType::Keyframe));
444        assert!(keyframe.is_keyframe());
445
446        // Inter frame AVC
447        let interframe = FlvTag::video(0, Bytes::from_static(&[0x27, 0x01]));
448        assert_eq!(
449            interframe.video_frame_type(),
450            Some(VideoFrameType::InterFrame)
451        );
452        assert!(!interframe.is_keyframe());
453
454        // Audio tag should return None
455        let audio = FlvTag::audio(0, Bytes::from_static(&[0xAF, 0x01]));
456        assert!(audio.video_frame_type().is_none());
457    }
458
459    #[test]
460    fn test_flv_tag_video_codec() {
461        // AVC codec
462        let avc = FlvTag::video(0, Bytes::from_static(&[0x17, 0x01]));
463        assert_eq!(avc.video_codec(), Some(VideoCodec::Avc));
464
465        // HEVC codec (enhanced RTMP)
466        let hevc = FlvTag::video(0, Bytes::from_static(&[0x1C, 0x01]));
467        assert_eq!(hevc.video_codec(), Some(VideoCodec::Hevc));
468
469        // Audio should return None
470        let audio = FlvTag::audio(0, Bytes::from_static(&[0xAF]));
471        assert!(audio.video_codec().is_none());
472    }
473
474    #[test]
475    fn test_flv_tag_audio_format() {
476        // AAC audio
477        let aac = FlvTag::audio(0, Bytes::from_static(&[0xAF, 0x01]));
478        assert_eq!(aac.audio_format(), Some(AudioFormat::Aac));
479
480        // MP3 audio
481        let mp3 = FlvTag::audio(0, Bytes::from_static(&[0x2F]));
482        assert_eq!(mp3.audio_format(), Some(AudioFormat::Mp3));
483
484        // Video should return None
485        let video = FlvTag::video(0, Bytes::from_static(&[0x17]));
486        assert!(video.audio_format().is_none());
487    }
488
489    #[test]
490    fn test_flv_tag_empty_data() {
491        let empty_video = FlvTag::video(0, Bytes::new());
492        assert!(empty_video.video_frame_type().is_none());
493        assert!(empty_video.video_codec().is_none());
494        assert!(!empty_video.is_keyframe());
495        assert!(!empty_video.is_avc_sequence_header());
496
497        let empty_audio = FlvTag::audio(0, Bytes::new());
498        assert!(empty_audio.audio_format().is_none());
499        assert!(!empty_audio.is_aac_sequence_header());
500    }
501
502    #[test]
503    fn test_flv_tag_size() {
504        let tag = FlvTag::video(0, Bytes::from_static(&[0x17, 0x00, 0x00, 0x00, 0x00]));
505        assert_eq!(tag.size(), 5);
506
507        let empty_tag = FlvTag::audio(0, Bytes::new());
508        assert_eq!(empty_tag.size(), 0);
509    }
510
511    #[test]
512    fn test_is_avc_sequence_header_non_avc() {
513        // Non-AVC codec (e.g., HEVC)
514        let hevc = FlvTag::video(0, Bytes::from_static(&[0x1C, 0x00, 0x00, 0x00, 0x00]));
515        assert!(!hevc.is_avc_sequence_header());
516
517        // AVC but not sequence header (packet type 1)
518        let avc_nalu = FlvTag::video(0, Bytes::from_static(&[0x17, 0x01, 0x00, 0x00, 0x00]));
519        assert!(!avc_nalu.is_avc_sequence_header());
520    }
521
522    #[test]
523    fn test_is_aac_sequence_header_non_aac() {
524        // Non-AAC format (e.g., MP3)
525        let mp3 = FlvTag::audio(0, Bytes::from_static(&[0x2F, 0x00]));
526        assert!(!mp3.is_aac_sequence_header());
527
528        // AAC but raw frame (packet type 1)
529        let aac_raw = FlvTag::audio(0, Bytes::from_static(&[0xAF, 0x01]));
530        assert!(!aac_raw.is_aac_sequence_header());
531    }
532
533    #[test]
534    fn test_flv_tag_type_enum() {
535        assert_eq!(FlvTagType::Audio, FlvTagType::Audio);
536        assert_ne!(FlvTagType::Audio, FlvTagType::Video);
537        assert_ne!(FlvTagType::Video, FlvTagType::Script);
538    }
539
540    #[test]
541    fn test_combined_video_byte() {
542        // Test decoding both frame type and codec from same byte
543        // 0x17 = keyframe (1) + AVC (7)
544        let tag = FlvTag::video(0, Bytes::from_static(&[0x17]));
545        assert_eq!(tag.video_frame_type(), Some(VideoFrameType::Keyframe));
546        assert_eq!(tag.video_codec(), Some(VideoCodec::Avc));
547
548        // 0x27 = inter frame (2) + AVC (7)
549        let tag = FlvTag::video(0, Bytes::from_static(&[0x27]));
550        assert_eq!(tag.video_frame_type(), Some(VideoFrameType::InterFrame));
551        assert_eq!(tag.video_codec(), Some(VideoCodec::Avc));
552
553        // 0x14 = keyframe (1) + VP6 (4)
554        let tag = FlvTag::video(0, Bytes::from_static(&[0x14]));
555        assert_eq!(tag.video_frame_type(), Some(VideoFrameType::Keyframe));
556        assert_eq!(tag.video_codec(), Some(VideoCodec::Vp6));
557    }
558
559    #[test]
560    fn test_short_video_data() {
561        // Only 1 byte - enough for frame type and codec
562        let tag = FlvTag::video(0, Bytes::from_static(&[0x17]));
563        assert!(tag.video_frame_type().is_some());
564        assert!(tag.video_codec().is_some());
565
566        // But not enough for sequence header check (needs 2 bytes)
567        assert!(!tag.is_avc_sequence_header());
568    }
569
570    #[test]
571    fn test_short_audio_data() {
572        // Only 1 byte - enough for format
573        let tag = FlvTag::audio(0, Bytes::from_static(&[0xAF]));
574        assert!(tag.audio_format().is_some());
575
576        // But not enough for sequence header check (needs 2 bytes)
577        assert!(!tag.is_aac_sequence_header());
578    }
579}