Skip to main content

rtmp_rs/media/
enhanced_video.rs

1//! Enhanced RTMP video parsing
2//!
3//! This module handles the Enhanced RTMP video format which uses FOURCC
4//! codec signaling and supports modern codecs like HEVC, AV1, and VP9.
5//!
6//! Enhanced video is signaled by the `isExVideoHeader` bit (bit 7 of first byte).
7//! When set, the lower 4 bits become `VideoPacketType` instead of codec ID.
8//!
9//! Reference: E-RTMP v2 specification - "Enhancing Video"
10
11use bytes::{Buf, Bytes};
12
13use crate::error::{MediaError, Result};
14use crate::media::fourcc::VideoFourCc;
15
16/// Enhanced video packet type (lower 4 bits when isExVideoHeader=1).
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[repr(u8)]
19pub enum VideoPacketType {
20    /// Sequence header (codec configuration)
21    SequenceStart = 0,
22    /// Coded video frames
23    CodedFrames = 1,
24    /// End of sequence
25    SequenceEnd = 2,
26    /// Coded frames with composition time = 0 (optimization)
27    CodedFramesX = 3,
28    /// Metadata (HDR info, etc.)
29    Metadata = 4,
30    /// MPEG-2 TS sequence start
31    Mpeg2TsSequenceStart = 5,
32    /// Multitrack video
33    Multitrack = 6,
34    /// ModEx signal (extensions follow)
35    ModEx = 7,
36}
37
38impl VideoPacketType {
39    /// Parse from the lower 4 bits of the first byte.
40    pub fn from_byte(b: u8) -> Option<Self> {
41        match b & 0x0F {
42            0 => Some(VideoPacketType::SequenceStart),
43            1 => Some(VideoPacketType::CodedFrames),
44            2 => Some(VideoPacketType::SequenceEnd),
45            3 => Some(VideoPacketType::CodedFramesX),
46            4 => Some(VideoPacketType::Metadata),
47            5 => Some(VideoPacketType::Mpeg2TsSequenceStart),
48            6 => Some(VideoPacketType::Multitrack),
49            7 => Some(VideoPacketType::ModEx),
50            _ => None,
51        }
52    }
53}
54
55/// Enhanced video frame type (bits 4-6 when isExVideoHeader=1).
56///
57/// Note: In enhanced mode, frame type is 3 bits (1-5 valid values).
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[repr(u8)]
60pub enum ExVideoFrameType {
61    /// Keyframe (IDR for AVC/HEVC, key for VP9/AV1)
62    Keyframe = 1,
63    /// Inter frame (P-frame)
64    InterFrame = 2,
65    /// Disposable inter frame
66    DisposableInterFrame = 3,
67    /// Generated keyframe (server-side)
68    GeneratedKeyframe = 4,
69    /// Command frame (video info)
70    CommandFrame = 5,
71}
72
73impl ExVideoFrameType {
74    /// Parse from bits 4-6 of the first byte.
75    pub fn from_byte(b: u8) -> Option<Self> {
76        match (b >> 4) & 0x07 {
77            1 => Some(ExVideoFrameType::Keyframe),
78            2 => Some(ExVideoFrameType::InterFrame),
79            3 => Some(ExVideoFrameType::DisposableInterFrame),
80            4 => Some(ExVideoFrameType::GeneratedKeyframe),
81            5 => Some(ExVideoFrameType::CommandFrame),
82            _ => None,
83        }
84    }
85
86    /// Check if this is a keyframe type.
87    pub fn is_keyframe(&self) -> bool {
88        matches!(
89            self,
90            ExVideoFrameType::Keyframe | ExVideoFrameType::GeneratedKeyframe
91        )
92    }
93}
94
95/// Multitrack type for video/audio.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[repr(u8)]
98pub enum AvMultitrackType {
99    /// Single track with track ID
100    OneTrack = 0,
101    /// Multiple tracks, same codec
102    ManyTracks = 1,
103    /// Multiple tracks, different codecs
104    ManyTracksManyCodecs = 2,
105}
106
107impl AvMultitrackType {
108    /// Parse from byte value.
109    pub fn from_byte(b: u8) -> Option<Self> {
110        match b {
111            0 => Some(AvMultitrackType::OneTrack),
112            1 => Some(AvMultitrackType::ManyTracks),
113            2 => Some(AvMultitrackType::ManyTracksManyCodecs),
114            _ => None,
115        }
116    }
117}
118
119/// Parsed enhanced video data.
120#[derive(Debug, Clone)]
121pub enum EnhancedVideoData {
122    /// Sequence header (codec configuration record).
123    SequenceHeader {
124        /// Video codec
125        codec: VideoFourCc,
126        /// Frame type (usually keyframe for sequence headers)
127        frame_type: ExVideoFrameType,
128        /// Codec-specific configuration data
129        config: Bytes,
130    },
131
132    /// Coded video frame.
133    Frame {
134        /// Video codec
135        codec: VideoFourCc,
136        /// Frame type
137        frame_type: ExVideoFrameType,
138        /// Composition time offset in milliseconds (for B-frames)
139        composition_time: i32,
140        /// Frame data (NALUs for AVC/HEVC, OBUs for AV1, etc.)
141        data: Bytes,
142    },
143
144    /// End of sequence marker.
145    SequenceEnd {
146        /// Video codec
147        codec: VideoFourCc,
148    },
149
150    /// Metadata frame (HDR info, etc.).
151    Metadata {
152        /// Raw metadata bytes (AMF encoded)
153        data: Bytes,
154    },
155
156    /// Multitrack video container.
157    Multitrack {
158        /// Multitrack type
159        multitrack_type: AvMultitrackType,
160        /// Individual tracks
161        tracks: Vec<VideoTrack>,
162    },
163}
164
165/// A single video track within a multitrack container.
166#[derive(Debug, Clone)]
167pub struct VideoTrack {
168    /// Track ID (0-255)
169    pub track_id: u8,
170    /// Video codec for this track
171    pub codec: VideoFourCc,
172    /// Track data
173    pub data: EnhancedVideoTrackData,
174}
175
176/// Data within a video track.
177#[derive(Debug, Clone)]
178pub enum EnhancedVideoTrackData {
179    /// Sequence header for this track
180    SequenceHeader { config: Bytes },
181    /// Coded frame for this track
182    Frame {
183        frame_type: ExVideoFrameType,
184        composition_time: i32,
185        data: Bytes,
186    },
187    /// Sequence end for this track
188    SequenceEnd,
189}
190
191impl EnhancedVideoData {
192    /// Check if video data uses enhanced format (isExVideoHeader bit set).
193    #[inline]
194    pub fn is_enhanced(first_byte: u8) -> bool {
195        (first_byte & 0x80) != 0
196    }
197
198    /// Parse enhanced video from RTMP video message payload.
199    ///
200    /// The payload should start with the FLV video tag header byte.
201    pub fn parse(data: Bytes) -> Result<Self> {
202        if data.is_empty() {
203            return Err(MediaError::InvalidEnhancedVideoPacket.into());
204        }
205
206        let first_byte = data[0];
207
208        // Verify this is enhanced video
209        if !Self::is_enhanced(first_byte) {
210            return Err(MediaError::InvalidEnhancedVideoPacket.into());
211        }
212
213        let frame_type = ExVideoFrameType::from_byte(first_byte)
214            .ok_or(MediaError::InvalidEnhancedVideoPacket)?;
215
216        let packet_type =
217            VideoPacketType::from_byte(first_byte).ok_or(MediaError::InvalidEnhancedVideoPacket)?;
218
219        let mut cursor = data.slice(1..);
220
221        // Handle packet types
222        match packet_type {
223            VideoPacketType::ModEx => {
224                // ModEx signals that extensions follow
225                // For now, skip ModEx bytes and parse the actual content
226                Self::parse_with_modex(cursor, frame_type)
227            }
228            VideoPacketType::Multitrack => Self::parse_multitrack(cursor, frame_type),
229            VideoPacketType::Metadata => Ok(EnhancedVideoData::Metadata { data: cursor }),
230            _ => {
231                // Regular enhanced video: FOURCC follows
232                if cursor.len() < 4 {
233                    return Err(MediaError::InvalidEnhancedVideoPacket.into());
234                }
235
236                let fourcc_bytes = [cursor[0], cursor[1], cursor[2], cursor[3]];
237                let codec = VideoFourCc::from_bytes(&fourcc_bytes)
238                    .ok_or(MediaError::UnsupportedVideoCodec)?;
239                cursor.advance(4);
240
241                Self::parse_by_packet_type(packet_type, codec, frame_type, cursor)
242            }
243        }
244    }
245
246    /// Parse with ModEx prefix handling.
247    fn parse_with_modex(mut data: Bytes, frame_type: ExVideoFrameType) -> Result<Self> {
248        // ModEx format: one or more extension bytes followed by actual packet
249        // For now, we just skip the ModEx byte and try to parse what follows
250        // A full implementation would parse TimestampOffsetNano etc.
251
252        if data.is_empty() {
253            return Err(MediaError::InvalidEnhancedVideoPacket.into());
254        }
255
256        let modex_type = data[0];
257        data.advance(1);
258
259        // TimestampOffsetNano (type 0) has 3 bytes of nanosecond offset
260        if modex_type == 0 {
261            if data.len() < 3 {
262                return Err(MediaError::InvalidEnhancedVideoPacket.into());
263            }
264            // Skip the 3-byte nanosecond offset for now
265            data.advance(3);
266        }
267
268        // After ModEx data, check if there's another packet type byte or FOURCC
269        if data.len() < 4 {
270            return Err(MediaError::InvalidEnhancedVideoPacket.into());
271        }
272
273        // Try to parse as FOURCC
274        let fourcc_bytes = [data[0], data[1], data[2], data[3]];
275        if let Some(codec) = VideoFourCc::from_bytes(&fourcc_bytes) {
276            data.advance(4);
277            // Assume CodedFramesX after ModEx (composition time = 0)
278            Ok(EnhancedVideoData::Frame {
279                codec,
280                frame_type,
281                composition_time: 0,
282                data,
283            })
284        } else {
285            Err(MediaError::InvalidEnhancedVideoPacket.into())
286        }
287    }
288
289    /// Parse multitrack video container.
290    fn parse_multitrack(mut data: Bytes, _frame_type: ExVideoFrameType) -> Result<Self> {
291        if data.is_empty() {
292            return Err(MediaError::InvalidEnhancedVideoPacket.into());
293        }
294
295        let multitrack_byte = data[0];
296        data.advance(1);
297
298        let multitrack_type = AvMultitrackType::from_byte(multitrack_byte >> 4)
299            .ok_or(MediaError::InvalidEnhancedVideoPacket)?;
300        let packet_type = VideoPacketType::from_byte(multitrack_byte)
301            .ok_or(MediaError::InvalidEnhancedVideoPacket)?;
302
303        let mut tracks = Vec::new();
304
305        match multitrack_type {
306            AvMultitrackType::OneTrack => {
307                // Single track: trackId (1 byte) + FOURCC (4 bytes) + data
308                if data.len() < 5 {
309                    return Err(MediaError::InvalidEnhancedVideoPacket.into());
310                }
311                let track_id = data[0];
312                data.advance(1);
313
314                let fourcc_bytes = [data[0], data[1], data[2], data[3]];
315                let codec = VideoFourCc::from_bytes(&fourcc_bytes)
316                    .ok_or(MediaError::UnsupportedVideoCodec)?;
317                data.advance(4);
318
319                let track_data = Self::parse_track_data(packet_type, &mut data)?;
320                tracks.push(VideoTrack {
321                    track_id,
322                    codec,
323                    data: track_data,
324                });
325            }
326            AvMultitrackType::ManyTracks => {
327                // Multiple tracks, same codec: FOURCC (4 bytes) then track entries
328                if data.len() < 4 {
329                    return Err(MediaError::InvalidEnhancedVideoPacket.into());
330                }
331                let fourcc_bytes = [data[0], data[1], data[2], data[3]];
332                let codec = VideoFourCc::from_bytes(&fourcc_bytes)
333                    .ok_or(MediaError::UnsupportedVideoCodec)?;
334                data.advance(4);
335
336                // Parse track entries until end of data
337                while !data.is_empty() {
338                    if data.len() < 4 {
339                        break;
340                    }
341                    let track_id = data[0];
342                    data.advance(1);
343
344                    let track_size =
345                        ((data[0] as usize) << 16) | ((data[1] as usize) << 8) | (data[2] as usize);
346                    data.advance(3);
347
348                    if data.len() < track_size {
349                        break;
350                    }
351                    let track_bytes = data.slice(..track_size);
352                    data.advance(track_size);
353
354                    tracks.push(VideoTrack {
355                        track_id,
356                        codec,
357                        data: EnhancedVideoTrackData::Frame {
358                            frame_type: ExVideoFrameType::InterFrame, // Default
359                            composition_time: 0,
360                            data: track_bytes,
361                        },
362                    });
363                }
364            }
365            AvMultitrackType::ManyTracksManyCodecs => {
366                // Multiple tracks with different codecs
367                while !data.is_empty() {
368                    if data.len() < 8 {
369                        break;
370                    }
371                    let track_id = data[0];
372                    data.advance(1);
373
374                    let fourcc_bytes = [data[0], data[1], data[2], data[3]];
375                    let codec = VideoFourCc::from_bytes(&fourcc_bytes)
376                        .ok_or(MediaError::UnsupportedVideoCodec)?;
377                    data.advance(4);
378
379                    let track_size =
380                        ((data[0] as usize) << 16) | ((data[1] as usize) << 8) | (data[2] as usize);
381                    data.advance(3);
382
383                    if data.len() < track_size {
384                        break;
385                    }
386                    let track_bytes = data.slice(..track_size);
387                    data.advance(track_size);
388
389                    tracks.push(VideoTrack {
390                        track_id,
391                        codec,
392                        data: EnhancedVideoTrackData::Frame {
393                            frame_type: ExVideoFrameType::InterFrame,
394                            composition_time: 0,
395                            data: track_bytes,
396                        },
397                    });
398                }
399            }
400        }
401
402        Ok(EnhancedVideoData::Multitrack {
403            multitrack_type,
404            tracks,
405        })
406    }
407
408    /// Parse track data based on packet type.
409    fn parse_track_data(
410        packet_type: VideoPacketType,
411        data: &mut Bytes,
412    ) -> Result<EnhancedVideoTrackData> {
413        match packet_type {
414            VideoPacketType::SequenceStart => Ok(EnhancedVideoTrackData::SequenceHeader {
415                config: data.clone(),
416            }),
417            VideoPacketType::SequenceEnd => Ok(EnhancedVideoTrackData::SequenceEnd),
418            VideoPacketType::CodedFramesX => Ok(EnhancedVideoTrackData::Frame {
419                frame_type: ExVideoFrameType::InterFrame,
420                composition_time: 0,
421                data: data.clone(),
422            }),
423            VideoPacketType::CodedFrames => {
424                if data.len() < 3 {
425                    return Err(MediaError::InvalidEnhancedVideoPacket.into());
426                }
427                let ct = read_si24(data)?;
428                Ok(EnhancedVideoTrackData::Frame {
429                    frame_type: ExVideoFrameType::InterFrame,
430                    composition_time: ct,
431                    data: data.clone(),
432                })
433            }
434            _ => Err(MediaError::InvalidEnhancedVideoPacket.into()),
435        }
436    }
437
438    /// Parse by specific packet type.
439    fn parse_by_packet_type(
440        packet_type: VideoPacketType,
441        codec: VideoFourCc,
442        frame_type: ExVideoFrameType,
443        mut data: Bytes,
444    ) -> Result<Self> {
445        match packet_type {
446            VideoPacketType::SequenceStart => Ok(EnhancedVideoData::SequenceHeader {
447                codec,
448                frame_type,
449                config: data,
450            }),
451            VideoPacketType::SequenceEnd => Ok(EnhancedVideoData::SequenceEnd { codec }),
452            VideoPacketType::CodedFramesX => {
453                // Composition time is implicitly 0
454                Ok(EnhancedVideoData::Frame {
455                    codec,
456                    frame_type,
457                    composition_time: 0,
458                    data,
459                })
460            }
461            VideoPacketType::CodedFrames => {
462                // SI24 composition time follows
463                if data.len() < 3 {
464                    return Err(MediaError::InvalidEnhancedVideoPacket.into());
465                }
466                let composition_time = read_si24(&mut data)?;
467                Ok(EnhancedVideoData::Frame {
468                    codec,
469                    frame_type,
470                    composition_time,
471                    data,
472                })
473            }
474            VideoPacketType::Mpeg2TsSequenceStart => {
475                // MPEG-2 TS format sequence header
476                Ok(EnhancedVideoData::SequenceHeader {
477                    codec,
478                    frame_type,
479                    config: data,
480                })
481            }
482            _ => Err(MediaError::InvalidEnhancedVideoPacket.into()),
483        }
484    }
485
486    /// Check if this is a keyframe.
487    pub fn is_keyframe(&self) -> bool {
488        match self {
489            EnhancedVideoData::SequenceHeader { frame_type, .. } => frame_type.is_keyframe(),
490            EnhancedVideoData::Frame { frame_type, .. } => frame_type.is_keyframe(),
491            EnhancedVideoData::SequenceEnd { .. } => false,
492            EnhancedVideoData::Metadata { .. } => false,
493            EnhancedVideoData::Multitrack { .. } => false, // Would need to check tracks
494        }
495    }
496
497    /// Check if this is a sequence header.
498    pub fn is_sequence_header(&self) -> bool {
499        matches!(self, EnhancedVideoData::SequenceHeader { .. })
500    }
501
502    /// Get the codec if available.
503    pub fn codec(&self) -> Option<VideoFourCc> {
504        match self {
505            EnhancedVideoData::SequenceHeader { codec, .. } => Some(*codec),
506            EnhancedVideoData::Frame { codec, .. } => Some(*codec),
507            EnhancedVideoData::SequenceEnd { codec } => Some(*codec),
508            EnhancedVideoData::Metadata { .. } => None,
509            EnhancedVideoData::Multitrack { .. } => None,
510        }
511    }
512}
513
514/// Read a signed 24-bit integer (SI24) from the buffer.
515fn read_si24(data: &mut Bytes) -> Result<i32> {
516    if data.len() < 3 {
517        return Err(MediaError::InvalidEnhancedVideoPacket.into());
518    }
519
520    let b0 = data.get_u8() as i32;
521    let b1 = data.get_u8() as i32;
522    let b2 = data.get_u8() as i32;
523
524    let value = (b0 << 16) | (b1 << 8) | b2;
525
526    // Sign extend from 24 bits
527    if value & 0x800000 != 0 {
528        Ok(value | !0xFFFFFF)
529    } else {
530        Ok(value)
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[test]
539    fn test_is_enhanced() {
540        // Enhanced video has bit 7 set
541        assert!(EnhancedVideoData::is_enhanced(0x80));
542        assert!(EnhancedVideoData::is_enhanced(0x90));
543        assert!(EnhancedVideoData::is_enhanced(0xFF));
544
545        // Legacy video has bit 7 clear
546        assert!(!EnhancedVideoData::is_enhanced(0x17)); // Keyframe + AVC
547        assert!(!EnhancedVideoData::is_enhanced(0x27)); // InterFrame + AVC
548        assert!(!EnhancedVideoData::is_enhanced(0x00));
549    }
550
551    #[test]
552    fn test_video_packet_type_parsing() {
553        assert_eq!(
554            VideoPacketType::from_byte(0x80),
555            Some(VideoPacketType::SequenceStart)
556        );
557        assert_eq!(
558            VideoPacketType::from_byte(0x81),
559            Some(VideoPacketType::CodedFrames)
560        );
561        assert_eq!(
562            VideoPacketType::from_byte(0x82),
563            Some(VideoPacketType::SequenceEnd)
564        );
565        assert_eq!(
566            VideoPacketType::from_byte(0x83),
567            Some(VideoPacketType::CodedFramesX)
568        );
569        assert_eq!(
570            VideoPacketType::from_byte(0x84),
571            Some(VideoPacketType::Metadata)
572        );
573        assert_eq!(
574            VideoPacketType::from_byte(0x85),
575            Some(VideoPacketType::Mpeg2TsSequenceStart)
576        );
577        assert_eq!(
578            VideoPacketType::from_byte(0x86),
579            Some(VideoPacketType::Multitrack)
580        );
581        assert_eq!(
582            VideoPacketType::from_byte(0x87),
583            Some(VideoPacketType::ModEx)
584        );
585
586        // Values 8-15 are reserved/invalid
587        assert_eq!(VideoPacketType::from_byte(0x88), None);
588        assert_eq!(VideoPacketType::from_byte(0x8F), None);
589    }
590
591    #[test]
592    fn test_ex_video_frame_type_parsing() {
593        // Frame type is in bits 4-6
594        assert_eq!(
595            ExVideoFrameType::from_byte(0x90),
596            Some(ExVideoFrameType::Keyframe)
597        ); // 0b1001_0000
598        assert_eq!(
599            ExVideoFrameType::from_byte(0xA0),
600            Some(ExVideoFrameType::InterFrame)
601        ); // 0b1010_0000
602        assert_eq!(
603            ExVideoFrameType::from_byte(0xB0),
604            Some(ExVideoFrameType::DisposableInterFrame)
605        );
606        assert_eq!(
607            ExVideoFrameType::from_byte(0xC0),
608            Some(ExVideoFrameType::GeneratedKeyframe)
609        );
610        assert_eq!(
611            ExVideoFrameType::from_byte(0xD0),
612            Some(ExVideoFrameType::CommandFrame)
613        );
614
615        // Frame type 0 is invalid
616        assert_eq!(ExVideoFrameType::from_byte(0x80), None);
617        // Frame types 6-7 are invalid
618        assert_eq!(ExVideoFrameType::from_byte(0xE0), None);
619        assert_eq!(ExVideoFrameType::from_byte(0xF0), None);
620    }
621
622    #[test]
623    fn test_frame_type_is_keyframe() {
624        assert!(ExVideoFrameType::Keyframe.is_keyframe());
625        assert!(ExVideoFrameType::GeneratedKeyframe.is_keyframe());
626        assert!(!ExVideoFrameType::InterFrame.is_keyframe());
627        assert!(!ExVideoFrameType::DisposableInterFrame.is_keyframe());
628        assert!(!ExVideoFrameType::CommandFrame.is_keyframe());
629    }
630
631    #[test]
632    fn test_multitrack_type_parsing() {
633        assert_eq!(
634            AvMultitrackType::from_byte(0),
635            Some(AvMultitrackType::OneTrack)
636        );
637        assert_eq!(
638            AvMultitrackType::from_byte(1),
639            Some(AvMultitrackType::ManyTracks)
640        );
641        assert_eq!(
642            AvMultitrackType::from_byte(2),
643            Some(AvMultitrackType::ManyTracksManyCodecs)
644        );
645        assert_eq!(AvMultitrackType::from_byte(3), None);
646    }
647
648    #[test]
649    fn test_parse_sequence_header() {
650        // Enhanced HEVC sequence header
651        // 0x90 = isExHeader(1) + Keyframe(001) + SequenceStart(0000)
652        let mut data = vec![0x90];
653        data.extend_from_slice(b"hvc1"); // FOURCC
654        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]); // Config data
655
656        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
657
658        match parsed {
659            EnhancedVideoData::SequenceHeader {
660                codec,
661                frame_type,
662                config,
663            } => {
664                assert_eq!(codec, VideoFourCc::Hevc);
665                assert_eq!(frame_type, ExVideoFrameType::Keyframe);
666                assert_eq!(config.as_ref(), &[0x01, 0x02, 0x03, 0x04]);
667            }
668            _ => panic!("Expected SequenceHeader"),
669        }
670    }
671
672    #[test]
673    fn test_parse_coded_frames_x() {
674        // Enhanced AV1 frame with CodedFramesX (no composition time)
675        // 0xA3 = isExHeader(1) + InterFrame(010) + CodedFramesX(0011)
676        let mut data = vec![0xA3];
677        data.extend_from_slice(b"av01"); // FOURCC
678        data.extend_from_slice(&[0xAA, 0xBB, 0xCC]); // Frame data
679
680        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
681
682        match parsed {
683            EnhancedVideoData::Frame {
684                codec,
685                frame_type,
686                composition_time,
687                data,
688            } => {
689                assert_eq!(codec, VideoFourCc::Av1);
690                assert_eq!(frame_type, ExVideoFrameType::InterFrame);
691                assert_eq!(composition_time, 0);
692                assert_eq!(data.as_ref(), &[0xAA, 0xBB, 0xCC]);
693            }
694            _ => panic!("Expected Frame"),
695        }
696    }
697
698    #[test]
699    fn test_parse_coded_frames_with_composition_time() {
700        // Enhanced VP9 frame with CodedFrames (has composition time)
701        // 0x91 = isExHeader(1) + Keyframe(001) + CodedFrames(0001)
702        let mut data = vec![0x91];
703        data.extend_from_slice(b"vp09"); // FOURCC
704        data.extend_from_slice(&[0x00, 0x01, 0x00]); // Composition time = 256
705        data.extend_from_slice(&[0xDD, 0xEE, 0xFF]); // Frame data
706
707        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
708
709        match parsed {
710            EnhancedVideoData::Frame {
711                codec,
712                frame_type,
713                composition_time,
714                data,
715            } => {
716                assert_eq!(codec, VideoFourCc::Vp9);
717                assert_eq!(frame_type, ExVideoFrameType::Keyframe);
718                assert_eq!(composition_time, 256);
719                assert_eq!(data.as_ref(), &[0xDD, 0xEE, 0xFF]);
720            }
721            _ => panic!("Expected Frame"),
722        }
723    }
724
725    #[test]
726    fn test_parse_negative_composition_time() {
727        // Frame with negative composition time
728        let mut data = vec![0x91]; // Keyframe + CodedFrames
729        data.extend_from_slice(b"avc1"); // FOURCC
730        data.extend_from_slice(&[0xFF, 0xFF, 0x00]); // -256 as SI24
731        data.extend_from_slice(&[0x11, 0x22]); // Frame data
732
733        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
734
735        match parsed {
736            EnhancedVideoData::Frame {
737                composition_time, ..
738            } => {
739                assert_eq!(composition_time, -256);
740            }
741            _ => panic!("Expected Frame"),
742        }
743    }
744
745    #[test]
746    fn test_parse_sequence_end() {
747        // Sequence end for HEVC
748        // 0x92 = isExHeader(1) + Keyframe(001) + SequenceEnd(0010)
749        let mut data = vec![0x92];
750        data.extend_from_slice(b"hvc1");
751
752        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
753
754        match parsed {
755            EnhancedVideoData::SequenceEnd { codec } => {
756                assert_eq!(codec, VideoFourCc::Hevc);
757            }
758            _ => panic!("Expected SequenceEnd"),
759        }
760    }
761
762    #[test]
763    fn test_parse_metadata() {
764        // Metadata packet (e.g., HDR info)
765        // 0x94 = isExHeader(1) + Keyframe(001) + Metadata(0100)
766        let data = vec![0x94, 0x01, 0x02, 0x03];
767
768        let parsed = EnhancedVideoData::parse(Bytes::from(data)).unwrap();
769
770        match parsed {
771            EnhancedVideoData::Metadata { data } => {
772                assert_eq!(data.as_ref(), &[0x01, 0x02, 0x03]);
773            }
774            _ => panic!("Expected Metadata"),
775        }
776    }
777
778    #[test]
779    fn test_parse_error_empty() {
780        let result = EnhancedVideoData::parse(Bytes::new());
781        assert!(result.is_err());
782    }
783
784    #[test]
785    fn test_parse_error_not_enhanced() {
786        // Legacy AVC - not enhanced
787        let data = Bytes::from_static(&[0x17, 0x00, 0x00, 0x00, 0x00]);
788        let result = EnhancedVideoData::parse(data);
789        assert!(result.is_err());
790    }
791
792    #[test]
793    fn test_parse_error_no_fourcc() {
794        // Enhanced header but no FOURCC
795        let data = Bytes::from_static(&[0x90, 0x01, 0x02]);
796        let result = EnhancedVideoData::parse(data);
797        assert!(result.is_err());
798    }
799
800    #[test]
801    fn test_parse_error_unknown_fourcc() {
802        // Enhanced header with unknown FOURCC
803        let mut data = vec![0x90];
804        data.extend_from_slice(b"xxxx"); // Unknown codec
805        let result = EnhancedVideoData::parse(Bytes::from(data));
806        assert!(result.is_err());
807    }
808
809    #[test]
810    fn test_is_keyframe() {
811        // Sequence header
812        let data = EnhancedVideoData::SequenceHeader {
813            codec: VideoFourCc::Hevc,
814            frame_type: ExVideoFrameType::Keyframe,
815            config: Bytes::new(),
816        };
817        assert!(data.is_keyframe());
818
819        // Keyframe
820        let data = EnhancedVideoData::Frame {
821            codec: VideoFourCc::Av1,
822            frame_type: ExVideoFrameType::Keyframe,
823            composition_time: 0,
824            data: Bytes::new(),
825        };
826        assert!(data.is_keyframe());
827
828        // Inter frame
829        let data = EnhancedVideoData::Frame {
830            codec: VideoFourCc::Av1,
831            frame_type: ExVideoFrameType::InterFrame,
832            composition_time: 0,
833            data: Bytes::new(),
834        };
835        assert!(!data.is_keyframe());
836
837        // Sequence end
838        let data = EnhancedVideoData::SequenceEnd {
839            codec: VideoFourCc::Hevc,
840        };
841        assert!(!data.is_keyframe());
842    }
843
844    #[test]
845    fn test_is_sequence_header() {
846        let header = EnhancedVideoData::SequenceHeader {
847            codec: VideoFourCc::Hevc,
848            frame_type: ExVideoFrameType::Keyframe,
849            config: Bytes::new(),
850        };
851        assert!(header.is_sequence_header());
852
853        let frame = EnhancedVideoData::Frame {
854            codec: VideoFourCc::Hevc,
855            frame_type: ExVideoFrameType::Keyframe,
856            composition_time: 0,
857            data: Bytes::new(),
858        };
859        assert!(!frame.is_sequence_header());
860    }
861
862    #[test]
863    fn test_codec_accessor() {
864        let header = EnhancedVideoData::SequenceHeader {
865            codec: VideoFourCc::Av1,
866            frame_type: ExVideoFrameType::Keyframe,
867            config: Bytes::new(),
868        };
869        assert_eq!(header.codec(), Some(VideoFourCc::Av1));
870
871        let metadata = EnhancedVideoData::Metadata { data: Bytes::new() };
872        assert_eq!(metadata.codec(), None);
873    }
874
875    #[test]
876    fn test_read_si24_positive() {
877        let mut data = Bytes::from_static(&[0x00, 0x01, 0x00]); // 256
878        assert_eq!(read_si24(&mut data).unwrap(), 256);
879
880        let mut data = Bytes::from_static(&[0x7F, 0xFF, 0xFF]); // Max positive
881        assert_eq!(read_si24(&mut data).unwrap(), 8388607);
882    }
883
884    #[test]
885    fn test_read_si24_negative() {
886        let mut data = Bytes::from_static(&[0xFF, 0xFF, 0x00]); // -256
887        assert_eq!(read_si24(&mut data).unwrap(), -256);
888
889        let mut data = Bytes::from_static(&[0x80, 0x00, 0x00]); // Min negative
890        assert_eq!(read_si24(&mut data).unwrap(), -8388608);
891    }
892
893    #[test]
894    fn test_read_si24_zero() {
895        let mut data = Bytes::from_static(&[0x00, 0x00, 0x00]);
896        assert_eq!(read_si24(&mut data).unwrap(), 0);
897    }
898
899    #[test]
900    fn test_read_si24_too_short() {
901        let mut data = Bytes::from_static(&[0x00, 0x01]);
902        assert!(read_si24(&mut data).is_err());
903    }
904}