Skip to main content

openipc_core/
rtp.rs

1use std::collections::BTreeMap;
2
3const DEFAULT_RTP_REORDER_WINDOW: usize = 15;
4const DEFAULT_MAX_ACCESS_UNIT_SIZE: usize = 8 * 1024 * 1024;
5
6/// Error returned while parsing or depacketizing RTP video.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RtpError {
9    /// Packet is shorter than the RTP fixed header or declared extension.
10    TooShort,
11    /// RTP version was not 2.
12    InvalidVersion(u8),
13    /// RTP extension header length is malformed.
14    InvalidExtension,
15    /// RTP padding length is malformed.
16    InvalidPadding,
17    /// Packet has no payload after header/extension/padding.
18    EmptyPayload,
19    /// Payload could not be interpreted as H.264 or H.265.
20    UnsupportedPayload,
21    /// Fragmented access unit exceeded the configured size guard.
22    FragmentOverflow,
23}
24
25/// Encoded video codec carried by a depacketized frame.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Codec {
28    /// H.264/AVC video.
29    H264,
30    /// H.265/HEVC video.
31    H265,
32}
33
34/// Dynamic RTP payload type used by OpenIPC for H.264.
35pub const RTP_PAYLOAD_TYPE_H264: u8 = 96;
36/// Dynamic RTP payload type used by OpenIPC for H.265.
37pub const RTP_PAYLOAD_TYPE_H265: u8 = 97;
38/// Dynamic RTP payload type used by OpenIPC/Majestic for Opus audio.
39pub const RTP_PAYLOAD_TYPE_OPUS: u8 = 98;
40
41/// Decoder configuration NAL units observed by the RTP depacketizer.
42///
43/// H.264 needs SPS and PPS before a decoder can be configured. H.265 needs
44/// VPS, SPS and PPS. PixelPilot starts its decoder as soon as these parameter
45/// sets have been observed, then feeds subsequent NAL units without requiring a
46/// fresh IDR for every startup path.
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
48pub struct CodecConfigState {
49    /// H.264 sequence parameter set has been seen.
50    pub h264_sps: bool,
51    /// H.264 picture parameter set has been seen.
52    pub h264_pps: bool,
53    /// H.265 video parameter set has been seen.
54    pub h265_vps: bool,
55    /// H.265 sequence parameter set has been seen.
56    pub h265_sps: bool,
57    /// H.265 picture parameter set has been seen.
58    pub h265_pps: bool,
59}
60
61impl CodecConfigState {
62    /// Return true when all parameter sets required for `codec` are cached.
63    pub const fn is_complete_for(self, codec: Codec) -> bool {
64        match codec {
65            Codec::H264 => self.h264_sps && self.h264_pps,
66            Codec::H265 => self.h265_vps && self.h265_sps && self.h265_pps,
67        }
68    }
69}
70
71/// Cumulative RTP depacketizer diagnostics.
72#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
73pub struct RtpDepacketizerStatus {
74    /// RTP packets submitted to the depacketizer.
75    pub packets: u64,
76    /// Annex-B video frames emitted.
77    pub frames_emitted: u64,
78    /// Video NAL units dropped because decoder config was not complete yet.
79    pub config_wait_drops: u64,
80    /// Keyframes emitted with cached decoder config prepended.
81    pub keyframes_with_prepended_config: u64,
82    /// Cached SPS/PPS/VPS parameter-set NAL units prepended to keyframes.
83    pub parameter_sets_prepended: u64,
84    /// Fragment chains dropped because an RTP sequence gap was observed.
85    pub fragment_sequence_gaps: u64,
86    /// Fragment chains that exceeded the configured size guard.
87    pub fragment_overflows: u64,
88    /// Packets rejected because they were not H.264/H.265 video.
89    pub unsupported_payloads: u64,
90    /// Packets rejected because the RTP header or payload was malformed.
91    pub malformed_packets: u64,
92    /// Most recent RTP payload type.
93    pub last_payload_type: Option<u8>,
94    /// Most recent RTP sequence number.
95    pub last_sequence_number: Option<u16>,
96    /// Most recent RTP timestamp.
97    pub last_timestamp: Option<u32>,
98    /// Most recent detected video codec.
99    pub last_codec: Option<Codec>,
100    /// Most recent H.264/H.265 NAL unit type.
101    pub last_nal_type: Option<u8>,
102    /// Current decoder configuration state.
103    pub codec_config: CodecConfigState,
104}
105
106/// Parsed RTP header metadata.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct RtpHeader {
109    /// RTP marker bit, usually set at an access-unit boundary.
110    pub marker: bool,
111    /// RTP payload type.
112    pub payload_type: u8,
113    /// RTP sequence number.
114    pub sequence_number: u16,
115    /// RTP timestamp.
116    pub timestamp: u32,
117    /// RTP synchronization source.
118    pub ssrc: u32,
119    /// Number of CSRC entries.
120    pub csrc_count: u8,
121    /// True if the packet has an RTP header extension.
122    pub has_extension: bool,
123    /// Header length including CSRC and extension bytes.
124    pub header_len: usize,
125    /// Payload length after header and padding removal.
126    pub payload_len: usize,
127}
128
129impl RtpHeader {
130    /// Parse an RTP header and validate extension/padding bounds.
131    pub fn parse(packet: &[u8]) -> Result<Self, RtpError> {
132        if packet.len() < 12 {
133            return Err(RtpError::TooShort);
134        }
135        let version = packet[0] >> 6;
136        if version != 2 {
137            return Err(RtpError::InvalidVersion(version));
138        }
139
140        let padding = packet[0] & 0x20 != 0;
141        let extension = packet[0] & 0x10 != 0;
142        let csrc_count = packet[0] & 0x0f;
143        let mut header_len = 12 + csrc_count as usize * 4;
144        if packet.len() < header_len {
145            return Err(RtpError::TooShort);
146        }
147
148        if extension {
149            if packet.len() < header_len + 4 {
150                return Err(RtpError::InvalidExtension);
151            }
152            let ext_words =
153                u16::from_be_bytes([packet[header_len + 2], packet[header_len + 3]]) as usize;
154            header_len += 4 + ext_words * 4;
155            if packet.len() < header_len {
156                return Err(RtpError::InvalidExtension);
157            }
158        }
159
160        let padding_len = if padding {
161            let len = *packet.last().ok_or(RtpError::InvalidPadding)? as usize;
162            if len == 0 || len > packet.len() - header_len {
163                return Err(RtpError::InvalidPadding);
164            }
165            len
166        } else {
167            0
168        };
169
170        let payload_len = packet.len() - header_len - padding_len;
171        if payload_len == 0 {
172            return Err(RtpError::EmptyPayload);
173        }
174
175        Ok(Self {
176            marker: packet[1] & 0x80 != 0,
177            payload_type: packet[1] & 0x7f,
178            sequence_number: u16::from_be_bytes([packet[2], packet[3]]),
179            timestamp: u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]),
180            ssrc: u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
181            csrc_count,
182            has_extension: extension,
183            header_len,
184            payload_len,
185        })
186    }
187
188    /// Borrow this packet's payload using the parsed header offsets.
189    pub fn payload<'a>(&self, packet: &'a [u8]) -> &'a [u8] {
190        &packet[self.header_len..self.header_len + self.payload_len]
191    }
192}
193
194/// Cumulative status for [`RtpReorderBuffer`].
195#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
196pub struct RtpReorderStatus {
197    /// Packets currently held while waiting for missing sequence numbers.
198    pub buffered_packets: usize,
199    /// Out-of-order packets accepted into the reorder window.
200    pub reordered_packets: u64,
201    /// Packets dropped because their sequence number was older than the window.
202    pub late_packets: u64,
203    /// Times the window flushed ahead after the missing packet did not arrive.
204    pub forced_flushes: u64,
205}
206
207/// Small RTP sequence reorder buffer.
208///
209/// PixelPilot keeps a short queue before its RTP parser so FU-A/FU fragments
210/// survive small USB/radio delivery inversions. This buffer does the same for
211/// the shared Rust receiver runtime while keeping the in-order path immediate.
212#[derive(Debug, Clone)]
213pub struct RtpReorderBuffer {
214    next_sequence: Option<u16>,
215    pending: BTreeMap<u16, Vec<u8>>,
216    max_depth: usize,
217    status: RtpReorderStatus,
218}
219
220impl Default for RtpReorderBuffer {
221    fn default() -> Self {
222        Self::new(DEFAULT_RTP_REORDER_WINDOW)
223    }
224}
225
226impl RtpReorderBuffer {
227    /// Create a reorder buffer with a maximum pending packet depth.
228    pub fn new(max_depth: usize) -> Self {
229        Self {
230            next_sequence: None,
231            pending: BTreeMap::new(),
232            max_depth: max_depth.max(1),
233            status: RtpReorderStatus::default(),
234        }
235    }
236
237    /// Push one RTP packet and return packets that are ready in sequence order.
238    pub fn push(&mut self, packet: &[u8]) -> Result<Vec<Vec<u8>>, RtpError> {
239        let header = RtpHeader::parse(packet)?;
240        let sequence = header.sequence_number;
241        let mut ready = Vec::new();
242
243        let Some(expected) = self.next_sequence else {
244            self.next_sequence = Some(sequence.wrapping_add(1));
245            ready.push(packet.to_vec());
246            return Ok(ready);
247        };
248
249        if sequence == expected {
250            self.next_sequence = Some(expected.wrapping_add(1));
251            ready.push(packet.to_vec());
252            self.drain_ready(&mut ready);
253            return Ok(ready);
254        }
255
256        if sequence_is_before(sequence, expected) {
257            self.status.late_packets = self.status.late_packets.saturating_add(1);
258            return Ok(ready);
259        }
260
261        if self.pending.insert(sequence, packet.to_vec()).is_none() {
262            self.status.reordered_packets = self.status.reordered_packets.saturating_add(1);
263        }
264        if self.pending.len() >= self.max_depth {
265            self.force_flush(expected, &mut ready);
266        }
267        self.status.buffered_packets = self.pending.len();
268        Ok(ready)
269    }
270
271    /// Return current reorder-buffer status.
272    pub fn status(&self) -> RtpReorderStatus {
273        RtpReorderStatus {
274            buffered_packets: self.pending.len(),
275            ..self.status
276        }
277    }
278
279    fn drain_ready(&mut self, ready: &mut Vec<Vec<u8>>) {
280        while let Some(expected) = self.next_sequence {
281            let Some(packet) = self.pending.remove(&expected) else {
282                break;
283            };
284            self.next_sequence = Some(expected.wrapping_add(1));
285            ready.push(packet);
286        }
287        self.status.buffered_packets = self.pending.len();
288    }
289
290    fn force_flush(&mut self, expected: u16, ready: &mut Vec<Vec<u8>>) {
291        let Some(sequence) = self
292            .pending
293            .keys()
294            .copied()
295            .min_by_key(|sequence| sequence.wrapping_sub(expected))
296        else {
297            return;
298        };
299        if let Some(packet) = self.pending.remove(&sequence) {
300            self.status.forced_flushes = self.status.forced_flushes.saturating_add(1);
301            self.next_sequence = Some(sequence.wrapping_add(1));
302            ready.push(packet);
303            self.drain_ready(ready);
304        }
305    }
306}
307
308fn sequence_is_before(sequence: u16, expected: u16) -> bool {
309    let backward = expected.wrapping_sub(sequence);
310    backward != 0 && backward < 0x8000
311}
312
313/// Encoded Annex-B H.264/H.265 access unit emitted by [`RtpDepacketizer`].
314#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct DepacketizedFrame {
316    /// Annex-B byte stream data including start codes.
317    pub data: Vec<u8>,
318    /// RTP timestamp associated with the access unit.
319    pub timestamp: u32,
320    /// True when the frame contains an IDR/keyframe entry point.
321    pub is_keyframe: bool,
322    /// Video codec for this frame.
323    pub codec: Codec,
324    /// RTP payload type that produced this frame.
325    pub payload_type: u8,
326    /// RTP sequence number of the packet that completed this frame.
327    pub sequence_number: u16,
328    /// H.264/H.265 NAL unit type for the frame payload.
329    pub nal_type: u8,
330    /// Decoder parameter-set state at the time this frame was emitted.
331    pub codec_config: CodecConfigState,
332}
333
334#[derive(Debug, Default, Clone)]
335struct FragmentState {
336    data: Vec<u8>,
337    timestamp: u32,
338    next_sequence: Option<u16>,
339    corrupted: bool,
340}
341
342#[derive(Debug, Default, Clone)]
343struct AccessUnitState {
344    data: Vec<u8>,
345    timestamp: Option<u32>,
346    next_sequence: Option<u16>,
347    corrupted: bool,
348    is_keyframe: bool,
349    has_decoder_config: bool,
350    nal_type: u8,
351}
352
353#[derive(Debug, Clone, Copy)]
354struct FrameMeta {
355    timestamp: u32,
356    is_keyframe: bool,
357    codec: Codec,
358    payload_type: u8,
359    sequence_number: u16,
360    nal_type: u8,
361}
362
363/// Stateful RTP depacketizer for OpenIPC H.264/H.265 video.
364///
365/// The depacketizer buffers fragmented NAL units, drops incomplete fragments
366/// across sequence gaps, and emits complete Annex-B access units.
367#[derive(Debug, Clone)]
368pub struct RtpDepacketizer {
369    h264: FragmentState,
370    h265: FragmentState,
371    h264_access_unit: AccessUnitState,
372    h265_access_unit: AccessUnitState,
373    h264_sps: Option<Vec<u8>>,
374    h264_pps: Option<Vec<u8>>,
375    h265_vps: Option<Vec<u8>>,
376    h265_sps: Option<Vec<u8>>,
377    h265_pps: Option<Vec<u8>>,
378    max_fragment: usize,
379    status: RtpDepacketizerStatus,
380}
381
382impl Default for RtpDepacketizer {
383    fn default() -> Self {
384        Self::new()
385    }
386}
387
388impl RtpDepacketizer {
389    /// Create a depacketizer with the default fragment-size guard.
390    pub fn new() -> Self {
391        Self {
392            h264: FragmentState::default(),
393            h265: FragmentState::default(),
394            h264_access_unit: AccessUnitState::default(),
395            h265_access_unit: AccessUnitState::default(),
396            h264_sps: None,
397            h264_pps: None,
398            h265_vps: None,
399            h265_sps: None,
400            h265_pps: None,
401            max_fragment: DEFAULT_MAX_ACCESS_UNIT_SIZE,
402            status: RtpDepacketizerStatus::default(),
403        }
404    }
405
406    /// Return cumulative depacketizer status and codec configuration state.
407    pub fn status(&self) -> RtpDepacketizerStatus {
408        RtpDepacketizerStatus {
409            codec_config: self.codec_config(),
410            ..self.status
411        }
412    }
413
414    /// Return the current decoder parameter-set state.
415    pub fn codec_config(&self) -> CodecConfigState {
416        CodecConfigState {
417            h264_sps: self.h264_sps.is_some(),
418            h264_pps: self.h264_pps.is_some(),
419            h265_vps: self.h265_vps.is_some(),
420            h265_sps: self.h265_sps.is_some(),
421            h265_pps: self.h265_pps.is_some(),
422        }
423    }
424
425    /// Push one RTP packet and return a complete frame when one is ready.
426    pub fn push(&mut self, packet: &[u8]) -> Result<Option<DepacketizedFrame>, RtpError> {
427        self.status.packets = self.status.packets.saturating_add(1);
428        let header = match RtpHeader::parse(packet) {
429            Ok(header) => header,
430            Err(err) => {
431                self.record_error(err);
432                return Err(err);
433            }
434        };
435        self.status.last_payload_type = Some(header.payload_type);
436        self.status.last_sequence_number = Some(header.sequence_number);
437        self.status.last_timestamp = Some(header.timestamp);
438        let payload = header.payload(packet);
439        log::trace!(
440            target: "openipc_core::rtp",
441            "received RTP packet sequence={} timestamp={} pt={} marker={} bytes={}",
442            header.sequence_number,
443            header.timestamp,
444            header.payload_type,
445            header.marker,
446            payload.len()
447        );
448        if header.payload_type == RTP_PAYLOAD_TYPE_OPUS {
449            self.record_error(RtpError::UnsupportedPayload);
450            return Err(RtpError::UnsupportedPayload);
451        }
452        let Some(codec) =
453            codec_from_payload_type(header.payload_type).or_else(|| detect_codec(payload))
454        else {
455            self.record_error(RtpError::UnsupportedPayload);
456            return Err(RtpError::UnsupportedPayload);
457        };
458        self.status.last_codec = Some(codec);
459        self.observe_access_unit_packet(codec, header);
460        let result = match codec {
461            Codec::H264 => self.push_h264(payload, header),
462            Codec::H265 => self.push_h265(payload, header),
463        };
464        match &result {
465            Ok(Some(_)) => {
466                self.status.frames_emitted = self.status.frames_emitted.saturating_add(1)
467            }
468            Err(err) => {
469                log::debug!(
470                    target: "openipc_core::rtp",
471                    "RTP packet rejected sequence={}: {err:?}",
472                    header.sequence_number
473                );
474                self.record_error(*err);
475            }
476            _ => {}
477        }
478        result
479    }
480
481    fn push_h264(
482        &mut self,
483        payload: &[u8],
484        header: RtpHeader,
485    ) -> Result<Option<DepacketizedFrame>, RtpError> {
486        let nal_type = payload[0] & 0x1f;
487        self.status.last_nal_type = Some(nal_type);
488        match nal_type {
489            7 => {
490                self.h264_sps = Some(payload.to_vec());
491                Ok(None)
492            }
493            8 => {
494                self.h264_pps = Some(payload.to_vec());
495                Ok(None)
496            }
497            24 => self.h264_stap_a(payload, header),
498            28 => self.h264_fu_a(payload, header),
499            _ if self.has_decoder_config(Codec::H264) && is_h264_vcl_nal(nal_type) => self
500                .push_complete_nalu(
501                    payload,
502                    FrameMeta {
503                        timestamp: header.timestamp,
504                        is_keyframe: nal_type == 5,
505                        codec: Codec::H264,
506                        payload_type: header.payload_type,
507                        sequence_number: header.sequence_number,
508                        nal_type,
509                    },
510                    header.marker,
511                ),
512            _ if !is_h264_vcl_nal(nal_type) => Ok(None),
513            _ => {
514                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
515                Ok(None)
516            }
517        }
518    }
519
520    fn push_h265(
521        &mut self,
522        payload: &[u8],
523        header: RtpHeader,
524    ) -> Result<Option<DepacketizedFrame>, RtpError> {
525        if payload.len() < 2 {
526            return Err(RtpError::UnsupportedPayload);
527        }
528        let nal_type = (payload[0] >> 1) & 0x3f;
529        self.status.last_nal_type = Some(nal_type);
530        match nal_type {
531            32 => {
532                self.h265_vps = Some(payload.to_vec());
533                Ok(None)
534            }
535            33 => {
536                self.h265_sps = Some(payload.to_vec());
537                Ok(None)
538            }
539            34 => {
540                self.h265_pps = Some(payload.to_vec());
541                Ok(None)
542            }
543            48 => self.h265_ap(payload, header),
544            49 => self.h265_fu(payload, header),
545            _ if self.has_decoder_config(Codec::H265) && is_h265_vcl_nal(nal_type) => self
546                .push_complete_nalu(
547                    payload,
548                    FrameMeta {
549                        timestamp: header.timestamp,
550                        is_keyframe: (16..=23).contains(&nal_type),
551                        codec: Codec::H265,
552                        payload_type: header.payload_type,
553                        sequence_number: header.sequence_number,
554                        nal_type,
555                    },
556                    header.marker,
557                ),
558            _ if !is_h265_vcl_nal(nal_type) => Ok(None),
559            _ => {
560                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
561                Ok(None)
562            }
563        }
564    }
565
566    fn h264_fu_a(
567        &mut self,
568        payload: &[u8],
569        header: RtpHeader,
570    ) -> Result<Option<DepacketizedFrame>, RtpError> {
571        if payload.len() < 2 {
572            return Err(RtpError::UnsupportedPayload);
573        }
574        let fu_indicator = payload[0];
575        let fu_header = payload[1];
576        let start = fu_header & 0x80 != 0;
577        let end = fu_header & 0x40 != 0;
578        let nal_type = fu_header & 0x1f;
579        if start {
580            self.h264.data.clear();
581            self.h264.data.extend_from_slice(&[0, 0, 0, 1]);
582            self.h264.timestamp = header.timestamp;
583            self.h264.next_sequence = Some(header.sequence_number.wrapping_add(1));
584            self.h264.corrupted = false;
585            self.h264.data.push((fu_indicator & 0xe0) | nal_type);
586        } else if !self.accept_fragment_sequence(Codec::H264, header.sequence_number) {
587            return Ok(None);
588        }
589        if !self.h264.corrupted {
590            self.append_fragment(Codec::H264, &payload[2..])?;
591        }
592        if end {
593            if !is_h264_vcl_nal(nal_type) {
594                self.reset_fragment(Codec::H264);
595                return Ok(None);
596            }
597            if self.h264.corrupted || !self.has_decoder_config(Codec::H264) {
598                if !self.has_decoder_config(Codec::H264) {
599                    self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
600                }
601                self.reset_fragment(Codec::H264);
602                return Ok(None);
603            }
604            let data = std::mem::take(&mut self.h264.data);
605            let meta = FrameMeta {
606                timestamp: self.h264.timestamp,
607                is_keyframe: nal_type == 5,
608                codec: Codec::H264,
609                payload_type: header.payload_type,
610                sequence_number: header.sequence_number,
611                nal_type,
612            };
613            self.reset_fragment(Codec::H264);
614            self.push_complete_owned_annex_b(data, meta, header.marker, false)
615        } else {
616            Ok(None)
617        }
618    }
619
620    fn h265_fu(
621        &mut self,
622        payload: &[u8],
623        header: RtpHeader,
624    ) -> Result<Option<DepacketizedFrame>, RtpError> {
625        if payload.len() < 3 {
626            return Err(RtpError::UnsupportedPayload);
627        }
628        let fu_header = payload[2];
629        let start = fu_header & 0x80 != 0;
630        let end = fu_header & 0x40 != 0;
631        let nal_type = fu_header & 0x3f;
632        if start {
633            self.h265.data.clear();
634            self.h265.data.extend_from_slice(&[0, 0, 0, 1]);
635            self.h265.timestamp = header.timestamp;
636            self.h265.next_sequence = Some(header.sequence_number.wrapping_add(1));
637            self.h265.corrupted = false;
638            self.h265.data.push((payload[0] & 0x81) | (nal_type << 1));
639            self.h265.data.push(payload[1]);
640        } else if !self.accept_fragment_sequence(Codec::H265, header.sequence_number) {
641            return Ok(None);
642        }
643        if !self.h265.corrupted {
644            self.append_fragment(Codec::H265, &payload[3..])?;
645        }
646        if end {
647            if !is_h265_vcl_nal(nal_type) {
648                self.reset_fragment(Codec::H265);
649                return Ok(None);
650            }
651            if self.h265.corrupted || !self.has_decoder_config(Codec::H265) {
652                if !self.has_decoder_config(Codec::H265) {
653                    self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
654                }
655                self.reset_fragment(Codec::H265);
656                return Ok(None);
657            }
658            let data = std::mem::take(&mut self.h265.data);
659            let meta = FrameMeta {
660                timestamp: self.h265.timestamp,
661                is_keyframe: (16..=23).contains(&nal_type),
662                codec: Codec::H265,
663                payload_type: header.payload_type,
664                sequence_number: header.sequence_number,
665                nal_type,
666            };
667            self.reset_fragment(Codec::H265);
668            self.push_complete_owned_annex_b(data, meta, header.marker, false)
669        } else {
670            Ok(None)
671        }
672    }
673
674    fn accept_fragment_sequence(&mut self, codec: Codec, sequence_number: u16) -> bool {
675        let state = match codec {
676            Codec::H264 => &mut self.h264,
677            Codec::H265 => &mut self.h265,
678        };
679        let Some(expected) = state.next_sequence else {
680            return false;
681        };
682        state.next_sequence = Some(sequence_number.wrapping_add(1));
683        if sequence_number != expected {
684            state.data.clear();
685            state.corrupted = true;
686            self.status.fragment_sequence_gaps =
687                self.status.fragment_sequence_gaps.saturating_add(1);
688            return false;
689        }
690        true
691    }
692
693    fn reset_fragment(&mut self, codec: Codec) {
694        let state = match codec {
695            Codec::H264 => &mut self.h264,
696            Codec::H265 => &mut self.h265,
697        };
698        state.data.clear();
699        state.next_sequence = None;
700        state.corrupted = false;
701    }
702
703    fn h264_stap_a(
704        &mut self,
705        payload: &[u8],
706        header: RtpHeader,
707    ) -> Result<Option<DepacketizedFrame>, RtpError> {
708        let mut out = Vec::new();
709        let mut offset = 1;
710        let mut keyframe = false;
711        let mut has_slice = false;
712        let mut has_sps = false;
713        let mut has_pps = false;
714        let mut last_slice_type = 0;
715        while offset + 2 <= payload.len() {
716            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
717            offset += 2;
718            if len == 0 || offset.saturating_add(len) > payload.len() {
719                return Err(RtpError::UnsupportedPayload);
720            }
721            let nalu = &payload[offset..offset + len];
722            let nal_type = nalu.first().map(|b| b & 0x1f).unwrap_or(0);
723            self.status.last_nal_type = Some(nal_type);
724            match nal_type {
725                7 => {
726                    has_sps = true;
727                    self.h264_sps = Some(nalu.to_vec());
728                }
729                8 => {
730                    has_pps = true;
731                    self.h264_pps = Some(nalu.to_vec());
732                }
733                _ => {}
734            }
735            if is_h264_vcl_nal(nal_type) {
736                has_slice = true;
737                keyframe |= nal_type == 5;
738                last_slice_type = nal_type;
739            }
740            append_annex_b(&mut out, nalu);
741            offset += len;
742        }
743        if offset != payload.len() {
744            return Err(RtpError::UnsupportedPayload);
745        }
746        if !has_slice || !self.has_decoder_config(Codec::H264) {
747            if has_slice {
748                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
749            }
750            return Ok(None);
751        }
752        self.push_complete_owned_annex_b(
753            out,
754            FrameMeta {
755                timestamp: header.timestamp,
756                is_keyframe: keyframe,
757                codec: Codec::H264,
758                payload_type: header.payload_type,
759                sequence_number: header.sequence_number,
760                nal_type: last_slice_type,
761            },
762            header.marker,
763            has_sps && has_pps,
764        )
765    }
766
767    fn h265_ap(
768        &mut self,
769        payload: &[u8],
770        header: RtpHeader,
771    ) -> Result<Option<DepacketizedFrame>, RtpError> {
772        let mut out = Vec::new();
773        let mut offset = 2;
774        let mut keyframe = false;
775        let mut has_slice = false;
776        let mut has_vps = false;
777        let mut has_sps = false;
778        let mut has_pps = false;
779        let mut last_slice_type = 0;
780        while offset + 2 <= payload.len() {
781            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
782            offset += 2;
783            if len == 0 || offset.saturating_add(len) > payload.len() {
784                return Err(RtpError::UnsupportedPayload);
785            }
786            let nalu = &payload[offset..offset + len];
787            let nal_type = nalu.first().map(|b| (b >> 1) & 0x3f).unwrap_or(0);
788            self.status.last_nal_type = Some(nal_type);
789            match nal_type {
790                32 => {
791                    has_vps = true;
792                    self.h265_vps = Some(nalu.to_vec());
793                }
794                33 => {
795                    has_sps = true;
796                    self.h265_sps = Some(nalu.to_vec());
797                }
798                34 => {
799                    has_pps = true;
800                    self.h265_pps = Some(nalu.to_vec());
801                }
802                _ => {}
803            }
804            if is_h265_vcl_nal(nal_type) {
805                has_slice = true;
806                keyframe |= (16..=23).contains(&nal_type);
807                last_slice_type = nal_type;
808            }
809            append_annex_b(&mut out, nalu);
810            offset += len;
811        }
812        if offset != payload.len() {
813            return Err(RtpError::UnsupportedPayload);
814        }
815        if !has_slice || !self.has_decoder_config(Codec::H265) {
816            if has_slice {
817                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
818            }
819            return Ok(None);
820        }
821        self.push_complete_owned_annex_b(
822            out,
823            FrameMeta {
824                timestamp: header.timestamp,
825                is_keyframe: keyframe,
826                codec: Codec::H265,
827                payload_type: header.payload_type,
828                sequence_number: header.sequence_number,
829                nal_type: last_slice_type,
830            },
831            header.marker,
832            has_vps && has_sps && has_pps,
833        )
834    }
835
836    fn append_fragment(&mut self, codec: Codec, bytes: &[u8]) -> Result<(), RtpError> {
837        let state = match codec {
838            Codec::H264 => &mut self.h264,
839            Codec::H265 => &mut self.h265,
840        };
841        if state.data.len() + bytes.len() > self.max_fragment {
842            self.status.fragment_overflows = self.status.fragment_overflows.saturating_add(1);
843            return Err(RtpError::FragmentOverflow);
844        }
845        state.data.extend_from_slice(bytes);
846        Ok(())
847    }
848
849    fn push_complete_nalu(
850        &mut self,
851        nalu: &[u8],
852        meta: FrameMeta,
853        marker: bool,
854    ) -> Result<Option<DepacketizedFrame>, RtpError> {
855        let annex_b_len = nalu.len().saturating_add(4);
856        if !self.prepare_access_unit_append(meta, marker, annex_b_len)? {
857            return Ok(None);
858        }
859        let state = self.access_unit_mut(meta.codec);
860        state.data.extend_from_slice(&[0, 0, 0, 1]);
861        state.data.extend_from_slice(nalu);
862        self.finish_access_unit(meta, marker, false)
863    }
864
865    fn push_complete_owned_annex_b(
866        &mut self,
867        annex_b: Vec<u8>,
868        meta: FrameMeta,
869        marker: bool,
870        has_decoder_config: bool,
871    ) -> Result<Option<DepacketizedFrame>, RtpError> {
872        if !self.prepare_access_unit_append(meta, marker, annex_b.len())? {
873            return Ok(None);
874        }
875        let state = self.access_unit_mut(meta.codec);
876        if state.data.is_empty() {
877            state.data = annex_b;
878        } else {
879            state.data.extend_from_slice(&annex_b);
880        }
881        self.finish_access_unit(meta, marker, has_decoder_config)
882    }
883
884    fn prepare_access_unit_append(
885        &mut self,
886        meta: FrameMeta,
887        marker: bool,
888        additional_len: usize,
889    ) -> Result<bool, RtpError> {
890        let max_fragment = self.max_fragment;
891        let state = self.access_unit_mut(meta.codec);
892        debug_assert_eq!(state.timestamp, Some(meta.timestamp));
893        if state.corrupted {
894            if marker {
895                reset_access_unit_state(state);
896            }
897            return Ok(false);
898        }
899        if state.data.len().saturating_add(additional_len) > max_fragment {
900            reset_access_unit_state(state);
901            self.status.fragment_overflows = self.status.fragment_overflows.saturating_add(1);
902            return Err(RtpError::FragmentOverflow);
903        }
904        state.data.reserve(additional_len);
905        Ok(true)
906    }
907
908    fn finish_access_unit(
909        &mut self,
910        meta: FrameMeta,
911        marker: bool,
912        has_decoder_config: bool,
913    ) -> Result<Option<DepacketizedFrame>, RtpError> {
914        let state = self.access_unit_mut(meta.codec);
915        state.is_keyframe |= meta.is_keyframe;
916        state.has_decoder_config |= has_decoder_config;
917        state.nal_type = meta.nal_type;
918        if !marker {
919            return Ok(None);
920        }
921
922        let mut data = std::mem::take(&mut state.data);
923        let is_keyframe = state.is_keyframe;
924        let has_decoder_config = state.has_decoder_config;
925        let nal_type = state.nal_type;
926        reset_access_unit_state(state);
927        if is_keyframe && !has_decoder_config {
928            let mut prefixed = Vec::with_capacity(data.len() + self.cached_config_len(meta.codec));
929            self.prepend_cached_config(&mut prefixed, meta.codec);
930            prefixed.append(&mut data);
931            data = prefixed;
932        }
933        Ok(Some(DepacketizedFrame {
934            data,
935            timestamp: meta.timestamp,
936            is_keyframe,
937            codec: meta.codec,
938            payload_type: meta.payload_type,
939            sequence_number: meta.sequence_number,
940            nal_type,
941            codec_config: self.codec_config(),
942        }))
943    }
944
945    fn access_unit_mut(&mut self, codec: Codec) -> &mut AccessUnitState {
946        match codec {
947            Codec::H264 => &mut self.h264_access_unit,
948            Codec::H265 => &mut self.h265_access_unit,
949        }
950    }
951
952    fn observe_access_unit_packet(&mut self, codec: Codec, header: RtpHeader) {
953        let state = match codec {
954            Codec::H264 => &mut self.h264_access_unit,
955            Codec::H265 => &mut self.h265_access_unit,
956        };
957        if state
958            .timestamp
959            .is_some_and(|timestamp| timestamp != header.timestamp)
960        {
961            if !state.data.is_empty() {
962                self.status.fragment_sequence_gaps =
963                    self.status.fragment_sequence_gaps.saturating_add(1);
964            }
965            reset_access_unit_state(state);
966        }
967        if state.timestamp.is_none() {
968            state.timestamp = Some(header.timestamp);
969        } else if state
970            .next_sequence
971            .is_some_and(|expected| expected != header.sequence_number)
972        {
973            if !state.corrupted {
974                self.status.fragment_sequence_gaps =
975                    self.status.fragment_sequence_gaps.saturating_add(1);
976            }
977            state.corrupted = true;
978            state.data.clear();
979        }
980        state.next_sequence = Some(header.sequence_number.wrapping_add(1));
981    }
982
983    fn cached_config_len(&self, codec: Codec) -> usize {
984        match codec {
985            Codec::H264 => {
986                self.h264_sps.as_ref().map_or(0, Vec::len)
987                    + self.h264_pps.as_ref().map_or(0, Vec::len)
988                    + 8
989            }
990            Codec::H265 => {
991                self.h265_vps.as_ref().map_or(0, Vec::len)
992                    + self.h265_sps.as_ref().map_or(0, Vec::len)
993                    + self.h265_pps.as_ref().map_or(0, Vec::len)
994                    + 12
995            }
996        }
997    }
998
999    fn prepend_cached_config(&mut self, data: &mut Vec<u8>, codec: Codec) {
1000        let mut prepended = 0u64;
1001        match codec {
1002            Codec::H264 => {
1003                if let Some(sps) = &self.h264_sps {
1004                    append_annex_b(data, sps);
1005                    prepended += 1;
1006                }
1007                if let Some(pps) = &self.h264_pps {
1008                    append_annex_b(data, pps);
1009                    prepended += 1;
1010                }
1011            }
1012            Codec::H265 => {
1013                if let Some(vps) = &self.h265_vps {
1014                    append_annex_b(data, vps);
1015                    prepended += 1;
1016                }
1017                if let Some(sps) = &self.h265_sps {
1018                    append_annex_b(data, sps);
1019                    prepended += 1;
1020                }
1021                if let Some(pps) = &self.h265_pps {
1022                    append_annex_b(data, pps);
1023                    prepended += 1;
1024                }
1025            }
1026        }
1027        if prepended > 0 {
1028            self.status.keyframes_with_prepended_config = self
1029                .status
1030                .keyframes_with_prepended_config
1031                .saturating_add(1);
1032            self.status.parameter_sets_prepended = self
1033                .status
1034                .parameter_sets_prepended
1035                .saturating_add(prepended);
1036        }
1037    }
1038
1039    fn has_decoder_config(&self, codec: Codec) -> bool {
1040        match codec {
1041            Codec::H264 => self.h264_sps.is_some() && self.h264_pps.is_some(),
1042            Codec::H265 => {
1043                self.h265_vps.is_some() && self.h265_sps.is_some() && self.h265_pps.is_some()
1044            }
1045        }
1046    }
1047
1048    fn record_error(&mut self, err: RtpError) {
1049        match err {
1050            RtpError::UnsupportedPayload => {
1051                self.status.unsupported_payloads =
1052                    self.status.unsupported_payloads.saturating_add(1);
1053            }
1054            RtpError::FragmentOverflow => {}
1055            _ => {
1056                self.status.malformed_packets = self.status.malformed_packets.saturating_add(1);
1057            }
1058        }
1059    }
1060}
1061
1062fn reset_access_unit_state(state: &mut AccessUnitState) {
1063    state.data.clear();
1064    state.timestamp = None;
1065    state.next_sequence = None;
1066    state.corrupted = false;
1067    state.is_keyframe = false;
1068    state.has_decoder_config = false;
1069    state.nal_type = 0;
1070}
1071
1072fn codec_from_payload_type(payload_type: u8) -> Option<Codec> {
1073    match payload_type {
1074        RTP_PAYLOAD_TYPE_H264 => Some(Codec::H264),
1075        RTP_PAYLOAD_TYPE_H265 => Some(Codec::H265),
1076        _ => None,
1077    }
1078}
1079
1080fn detect_codec(payload: &[u8]) -> Option<Codec> {
1081    if payload.is_empty() {
1082        return None;
1083    }
1084    if payload.len() >= 2 {
1085        let h265_nal_type = (payload[0] >> 1) & 0x3f;
1086        if h265_nal_type == 48 || h265_nal_type == 49 || (32..=40).contains(&h265_nal_type) {
1087            return Some(Codec::H265);
1088        }
1089    }
1090    let h264_nal_type = payload[0] & 0x1f;
1091    if h264_nal_type == 24 || h264_nal_type == 28 || (1..=12).contains(&h264_nal_type) {
1092        return Some(Codec::H264);
1093    }
1094    None
1095}
1096
1097fn is_h264_vcl_nal(nal_type: u8) -> bool {
1098    (1..=5).contains(&nal_type)
1099}
1100
1101fn is_h265_vcl_nal(nal_type: u8) -> bool {
1102    nal_type <= 31
1103}
1104
1105fn append_annex_b(out: &mut Vec<u8>, nalu: &[u8]) {
1106    out.extend_from_slice(&[0, 0, 0, 1]);
1107    out.extend_from_slice(nalu);
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113
1114    fn rtp(payload: &[u8], marker: bool, seq: u16, timestamp: u32) -> Vec<u8> {
1115        rtp_with_payload_type(payload, RTP_PAYLOAD_TYPE_H264, marker, seq, timestamp)
1116    }
1117
1118    fn rtp_with_payload_type(
1119        payload: &[u8],
1120        payload_type: u8,
1121        marker: bool,
1122        seq: u16,
1123        timestamp: u32,
1124    ) -> Vec<u8> {
1125        let mut packet = vec![
1126            0x80,
1127            (if marker { 0x80 } else { 0x00 }) | (payload_type & 0x7f),
1128        ];
1129        packet.extend_from_slice(&seq.to_be_bytes());
1130        packet.extend_from_slice(&timestamp.to_be_bytes());
1131        packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
1132        packet.extend_from_slice(payload);
1133        packet
1134    }
1135
1136    fn stap_a(units: &[&[u8]]) -> Vec<u8> {
1137        let mut payload = vec![24];
1138        for unit in units {
1139            payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
1140            payload.extend_from_slice(unit);
1141        }
1142        payload
1143    }
1144
1145    fn h265_ap(units: &[&[u8]]) -> Vec<u8> {
1146        let mut payload = vec![0x60, 0x01];
1147        for unit in units {
1148            payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
1149            payload.extend_from_slice(unit);
1150        }
1151        payload
1152    }
1153
1154    fn prime_h264(depay: &mut RtpDepacketizer) {
1155        assert!(depay
1156            .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
1157            .unwrap()
1158            .is_none());
1159        assert!(depay
1160            .push(&rtp(&[0x68, 0xee], true, 2, 10))
1161            .unwrap()
1162            .is_none());
1163    }
1164
1165    fn prime_h265(depay: &mut RtpDepacketizer) {
1166        for (seq, payload) in [
1167            (1, &[0x40, 0x01, 0xaa][..]),
1168            (2, &[0x42, 0x01, 0xbb][..]),
1169            (3, &[0x44, 0x01, 0xcc][..]),
1170        ] {
1171            assert!(depay
1172                .push(&rtp_with_payload_type(
1173                    payload,
1174                    RTP_PAYLOAD_TYPE_H265,
1175                    true,
1176                    seq,
1177                    10,
1178                ))
1179                .unwrap()
1180                .is_none());
1181        }
1182    }
1183
1184    #[test]
1185    fn parses_rtp_header() {
1186        let packet = rtp(&[0x65, 1, 2], true, 7, 1234);
1187        let header = RtpHeader::parse(&packet).unwrap();
1188        assert!(header.marker);
1189        assert_eq!(header.payload_type, 96);
1190        assert_eq!(header.sequence_number, 7);
1191        assert_eq!(header.timestamp, 1234);
1192        assert_eq!(header.payload(&packet), &[0x65, 1, 2]);
1193    }
1194
1195    #[test]
1196    fn depacketizes_h264_single_nalu_as_annex_b() {
1197        let mut depay = RtpDepacketizer::new();
1198        prime_h264(&mut depay);
1199        let frame = depay
1200            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
1201            .unwrap()
1202            .unwrap();
1203        assert_eq!(frame.codec, Codec::H264);
1204        assert!(frame.is_keyframe);
1205        assert_eq!(
1206            frame.data,
1207            [
1208                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
1209                &[0, 0, 0, 1, 0x68, 0xee][..],
1210                &[0, 0, 0, 1, 0x65, 0xaa][..],
1211            ]
1212            .concat()
1213        );
1214    }
1215
1216    #[test]
1217    fn combines_same_timestamp_h264_slices_until_marker() {
1218        let mut depay = RtpDepacketizer::new();
1219        prime_h264(&mut depay);
1220        assert!(depay
1221            .push(&rtp(&[0x41, 0x80, 0xaa], false, 3, 42))
1222            .unwrap()
1223            .is_none());
1224        let frame = depay
1225            .push(&rtp(&[0x41, 0x40, 0xbb], true, 4, 42))
1226            .unwrap()
1227            .unwrap();
1228
1229        assert_eq!(
1230            frame.data,
1231            [
1232                &[0, 0, 0, 1, 0x41, 0x80, 0xaa][..],
1233                &[0, 0, 0, 1, 0x41, 0x40, 0xbb][..],
1234            ]
1235            .concat()
1236        );
1237        assert_eq!(frame.timestamp, 42);
1238        assert!(!frame.is_keyframe);
1239    }
1240
1241    #[test]
1242    fn drops_access_unit_after_sequence_gap() {
1243        let mut depay = RtpDepacketizer::new();
1244        prime_h264(&mut depay);
1245        assert!(depay
1246            .push(&rtp(&[0x41, 0x80, 0xaa], false, 3, 42))
1247            .unwrap()
1248            .is_none());
1249        assert!(depay
1250            .push(&rtp(&[0x41, 0x40, 0xbb], true, 5, 42))
1251            .unwrap()
1252            .is_none());
1253
1254        assert_eq!(depay.status().fragment_sequence_gaps, 1);
1255        assert!(depay
1256            .push(&rtp(&[0x41, 0xcc], true, 6, 43))
1257            .unwrap()
1258            .is_some());
1259    }
1260
1261    #[test]
1262    fn drops_h264_video_until_sps_and_pps_are_seen() {
1263        let mut depay = RtpDepacketizer::new();
1264        assert!(depay
1265            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
1266            .unwrap()
1267            .is_none());
1268        let status = depay.status();
1269        assert_eq!(status.config_wait_drops, 1);
1270        assert!(!status.codec_config.is_complete_for(Codec::H264));
1271        assert_eq!(status.last_nal_type, Some(5));
1272    }
1273
1274    #[test]
1275    fn h264_payload_type_prevents_h265_false_positive() {
1276        let mut depay = RtpDepacketizer::new();
1277        prime_h264(&mut depay);
1278        let frame = depay
1279            .push(&rtp(&[0x41, 0xaa], true, 1, 42))
1280            .unwrap()
1281            .unwrap();
1282        assert_eq!(frame.codec, Codec::H264);
1283        assert!(!frame.is_keyframe);
1284        assert_eq!(frame.data, &[0, 0, 0, 1, 0x41, 0xaa]);
1285    }
1286
1287    #[test]
1288    fn h264_non_vcl_nal_is_not_emitted_as_video_frame() {
1289        let mut depay = RtpDepacketizer::new();
1290        prime_h264(&mut depay);
1291        assert!(depay
1292            .push(&rtp(&[0x06, 0x05, 0xff], true, 3, 42))
1293            .unwrap()
1294            .is_none());
1295    }
1296
1297    #[test]
1298    fn opus_payload_type_is_not_sniffed_as_video() {
1299        let mut depay = RtpDepacketizer::new();
1300        prime_h264(&mut depay);
1301        let err = depay
1302            .push(&rtp_with_payload_type(
1303                &[0x65, 0xaa],
1304                RTP_PAYLOAD_TYPE_OPUS,
1305                true,
1306                1,
1307                42,
1308            ))
1309            .unwrap_err();
1310        assert_eq!(err, RtpError::UnsupportedPayload);
1311    }
1312
1313    #[test]
1314    fn depacketizes_h265_single_nalu_by_payload_type() {
1315        let mut depay = RtpDepacketizer::new();
1316        prime_h265(&mut depay);
1317        let frame = depay
1318            .push(&rtp_with_payload_type(
1319                &[0x02, 0x01, 0xaa],
1320                RTP_PAYLOAD_TYPE_H265,
1321                true,
1322                1,
1323                42,
1324            ))
1325            .unwrap()
1326            .unwrap();
1327        assert_eq!(frame.codec, Codec::H265);
1328        assert!(!frame.is_keyframe);
1329        assert_eq!(frame.data, &[0, 0, 0, 1, 0x02, 0x01, 0xaa]);
1330    }
1331
1332    #[test]
1333    fn h265_non_vcl_nal_is_not_emitted_as_video_frame() {
1334        let mut depay = RtpDepacketizer::new();
1335        prime_h265(&mut depay);
1336        assert!(depay
1337            .push(&rtp_with_payload_type(
1338                &[0x4e, 0x01, 0xff],
1339                RTP_PAYLOAD_TYPE_H265,
1340                true,
1341                4,
1342                42,
1343            ))
1344            .unwrap()
1345            .is_none());
1346    }
1347
1348    #[test]
1349    fn h264_stap_a_caches_parameter_sets_for_later_keyframe() {
1350        let mut depay = RtpDepacketizer::new();
1351        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1352        let pps = &[0x68, 0xee][..];
1353        let aggregate = depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
1354        assert!(aggregate.is_none());
1355
1356        let frame = depay
1357            .push(&rtp(&[0x65, 0xaa], true, 2, 20))
1358            .unwrap()
1359            .unwrap();
1360        assert!(frame.is_keyframe);
1361        assert_eq!(
1362            frame.data,
1363            [
1364                &[0, 0, 0, 1][..],
1365                sps,
1366                &[0, 0, 0, 1][..],
1367                pps,
1368                &[0, 0, 0, 1, 0x65, 0xaa][..],
1369            ]
1370            .concat()
1371        );
1372    }
1373
1374    #[test]
1375    fn h264_stap_a_prepends_cached_parameter_sets_for_idr_without_inband_config() {
1376        let mut depay = RtpDepacketizer::new();
1377        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1378        let pps = &[0x68, 0xee][..];
1379        depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
1380
1381        let frame = depay
1382            .push(&rtp(&stap_a(&[&[0x65, 0xaa, 0xbb]]), true, 2, 20))
1383            .unwrap()
1384            .unwrap();
1385
1386        assert!(frame.is_keyframe);
1387        assert_eq!(
1388            frame.data,
1389            [
1390                &[0, 0, 0, 1][..],
1391                sps,
1392                &[0, 0, 0, 1][..],
1393                pps,
1394                &[0, 0, 0, 1, 0x65, 0xaa, 0xbb][..],
1395            ]
1396            .concat()
1397        );
1398        let status = depay.status();
1399        assert_eq!(status.keyframes_with_prepended_config, 1);
1400        assert_eq!(status.parameter_sets_prepended, 2);
1401    }
1402
1403    #[test]
1404    fn h264_stap_a_does_not_duplicate_inband_parameter_sets() {
1405        let mut depay = RtpDepacketizer::new();
1406        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1407        let pps = &[0x68, 0xee][..];
1408        let frame = depay
1409            .push(&rtp(&stap_a(&[sps, pps, &[0x65, 0xaa]]), true, 1, 20))
1410            .unwrap()
1411            .unwrap();
1412
1413        assert_eq!(
1414            frame.data,
1415            [
1416                &[0, 0, 0, 1][..],
1417                sps,
1418                &[0, 0, 0, 1][..],
1419                pps,
1420                &[0, 0, 0, 1, 0x65, 0xaa][..],
1421            ]
1422            .concat()
1423        );
1424        let status = depay.status();
1425        assert_eq!(status.keyframes_with_prepended_config, 0);
1426        assert_eq!(status.parameter_sets_prepended, 0);
1427    }
1428
1429    #[test]
1430    fn h264_stap_a_waits_for_the_access_unit_marker() {
1431        let mut depay = RtpDepacketizer::new();
1432        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1433        let pps = &[0x68, 0xee][..];
1434        assert!(depay
1435            .push(&rtp(&stap_a(&[sps, pps, &[0x61, 0xaa]]), false, 1, 20,))
1436            .unwrap()
1437            .is_none());
1438
1439        let frame = depay
1440            .push(&rtp(&[0x61, 0xbb], true, 2, 20))
1441            .unwrap()
1442            .unwrap();
1443        assert_eq!(
1444            frame.data,
1445            [
1446                &[0, 0, 0, 1][..],
1447                sps,
1448                &[0, 0, 0, 1][..],
1449                pps,
1450                &[0, 0, 0, 1, 0x61, 0xaa][..],
1451                &[0, 0, 0, 1, 0x61, 0xbb][..],
1452            ]
1453            .concat()
1454        );
1455    }
1456
1457    #[test]
1458    fn malformed_stap_a_length_is_rejected() {
1459        let mut depay = RtpDepacketizer::new();
1460        let malformed = [24, 0, 8, 0x67, 0x64];
1461        assert_eq!(
1462            depay.push(&rtp(&malformed, true, 1, 20)),
1463            Err(RtpError::UnsupportedPayload)
1464        );
1465        assert_eq!(depay.status().unsupported_payloads, 1);
1466    }
1467
1468    #[test]
1469    fn h265_ap_prepends_cached_parameter_sets_for_keyframe_without_inband_config() {
1470        let mut depay = RtpDepacketizer::new();
1471        prime_h265(&mut depay);
1472        let frame = depay
1473            .push(&rtp_with_payload_type(
1474                &h265_ap(&[&[0x26, 0x01, 0xaa]]),
1475                RTP_PAYLOAD_TYPE_H265,
1476                true,
1477                4,
1478                20,
1479            ))
1480            .unwrap()
1481            .unwrap();
1482
1483        assert!(frame.is_keyframe);
1484        assert_eq!(
1485            frame.data,
1486            [
1487                &[0, 0, 0, 1, 0x40, 0x01, 0xaa][..],
1488                &[0, 0, 0, 1, 0x42, 0x01, 0xbb][..],
1489                &[0, 0, 0, 1, 0x44, 0x01, 0xcc][..],
1490                &[0, 0, 0, 1, 0x26, 0x01, 0xaa][..],
1491            ]
1492            .concat()
1493        );
1494        let status = depay.status();
1495        assert_eq!(status.keyframes_with_prepended_config, 1);
1496        assert_eq!(status.parameter_sets_prepended, 3);
1497    }
1498
1499    #[test]
1500    fn waybeam_separate_parameter_sets_bootstrap_an_idr() {
1501        // Mirrors waybeam_venc's hevc_rtp test contract: PT 97, no type-48
1502        // aggregation, separate VPS/SPS/PPS packets, and marker only on IDR.
1503        let packets: [&[u8]; 4] = [
1504            &[
1505                0x80, 0x61, 0x01, 0x00, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x40, 0x01, 0xaa,
1506            ],
1507            &[
1508                0x80, 0x61, 0x01, 0x01, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x42, 0x01, 0xbb,
1509            ],
1510            &[
1511                0x80, 0x61, 0x01, 0x02, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x44, 0x01, 0xcc,
1512            ],
1513            &[
1514                0x80, 0xe1, 0x01, 0x03, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x26, 0x01, 0xdd,
1515            ],
1516        ];
1517        let mut depay = RtpDepacketizer::new();
1518        let mut frame = None;
1519        for packet in packets {
1520            if let Some(output) = depay.push(packet).unwrap() {
1521                frame = Some(output);
1522            }
1523        }
1524
1525        let frame = frame.expect("Waybeam IDR should produce an access unit");
1526        assert_eq!(frame.codec, Codec::H265);
1527        assert!(frame.is_keyframe);
1528        assert_eq!(
1529            frame.data,
1530            [
1531                &[0, 0, 0, 1, 0x40, 0x01, 0xaa][..],
1532                &[0, 0, 0, 1, 0x42, 0x01, 0xbb][..],
1533                &[0, 0, 0, 1, 0x44, 0x01, 0xcc][..],
1534                &[0, 0, 0, 1, 0x26, 0x01, 0xdd][..],
1535            ]
1536            .concat()
1537        );
1538    }
1539
1540    #[test]
1541    fn waybeam_hevc_fu_vector_round_trips() {
1542        let mut depay = RtpDepacketizer::new();
1543        for (sequence, payload) in [
1544            (0x1ffd, &[0x40, 0x01, 0xaa][..]),
1545            (0x1ffe, &[0x42, 0x01, 0xbb][..]),
1546            (0x1fff, &[0x44, 0x01, 0xcc][..]),
1547        ] {
1548            depay
1549                .push(&rtp_with_payload_type(
1550                    payload,
1551                    RTP_PAYLOAD_TYPE_H265,
1552                    false,
1553                    sequence,
1554                    0x0101_0100,
1555                ))
1556                .unwrap();
1557        }
1558
1559        // Literal output from Waybeam's max_payload=5 FU test for the NAL
1560        // 26 01 aa bb cc dd ee. The final packet alone carries the marker.
1561        let fragments: [&[u8]; 3] = [
1562            &[
1563                0x80, 0x61, 0x20, 0x00, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x93, 0xaa, 0xbb,
1564            ],
1565            &[
1566                0x80, 0x61, 0x20, 0x01, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x13, 0xcc, 0xdd,
1567            ],
1568            &[
1569                0x80, 0xe1, 0x20, 0x02, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x53, 0xee,
1570            ],
1571        ];
1572        let mut frame = None;
1573        for packet in fragments {
1574            if let Some(output) = depay.push(packet).unwrap() {
1575                frame = Some(output);
1576            }
1577        }
1578
1579        let frame = frame.expect("Waybeam FU chain should produce an access unit");
1580        assert_eq!(frame.codec, Codec::H265);
1581        assert!(frame.is_keyframe);
1582        assert!(frame
1583            .data
1584            .ends_with(&[0, 0, 0, 1, 0x26, 0x01, 0xaa, 0xbb, 0xcc, 0xdd, 0xee]));
1585    }
1586
1587    #[test]
1588    fn waybeam_refpred_trail_n_fu_is_accepted() {
1589        let mut depay = RtpDepacketizer::new();
1590        prime_h265(&mut depay);
1591        let fragments: [&[u8]; 2] = [
1592            &[
1593                0x80, 0x61, 0x07, 0x00, 0, 0, 0x70, 0, 0, 0, 0x43, 0x21, 0x62, 0x01, 0x80, 0x55,
1594                0x66,
1595            ],
1596            &[
1597                0x80, 0xe1, 0x07, 0x01, 0, 0, 0x70, 0, 0, 0, 0x43, 0x21, 0x62, 0x01, 0x40, 0x77,
1598                0x88,
1599            ],
1600        ];
1601        let mut frame = None;
1602        for packet in fragments {
1603            if let Some(output) = depay.push(packet).unwrap() {
1604                frame = Some(output);
1605            }
1606        }
1607
1608        let frame = frame.expect("Waybeam TRAIL_N FU chain should be emitted");
1609        assert_eq!(frame.codec, Codec::H265);
1610        assert!(!frame.is_keyframe);
1611        assert_eq!(frame.nal_type, 0);
1612        assert_eq!(
1613            frame.data,
1614            &[0, 0, 0, 1, 0x00, 0x01, 0x55, 0x66, 0x77, 0x88]
1615        );
1616    }
1617
1618    #[test]
1619    fn depacketizes_h264_fu_a() {
1620        let mut depay = RtpDepacketizer::new();
1621        prime_h264(&mut depay);
1622        assert!(depay
1623            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 1, 99))
1624            .unwrap()
1625            .is_none());
1626        let frame = depay
1627            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 2, 99))
1628            .unwrap()
1629            .unwrap();
1630        assert_eq!(
1631            frame.data,
1632            [
1633                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
1634                &[0, 0, 0, 1, 0x68, 0xee][..],
1635                &[0, 0, 0, 1, 0x65, 1, 2, 3, 4][..],
1636            ]
1637            .concat()
1638        );
1639    }
1640
1641    #[test]
1642    fn drops_h264_fu_a_after_sequence_gap() {
1643        let mut depay = RtpDepacketizer::new();
1644        prime_h264(&mut depay);
1645        assert!(depay
1646            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 10, 99))
1647            .unwrap()
1648            .is_none());
1649        assert!(depay
1650            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 12, 99))
1651            .unwrap()
1652            .is_none());
1653
1654        assert!(depay
1655            .push(&rtp(&[0x7c, 0x85, 5, 6], false, 13, 100))
1656            .unwrap()
1657            .is_none());
1658        let frame = depay
1659            .push(&rtp(&[0x7c, 0x45, 7, 8], true, 14, 100))
1660            .unwrap()
1661            .unwrap();
1662        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 5, 6, 7, 8]));
1663    }
1664
1665    #[test]
1666    fn drops_fragment_end_without_start() {
1667        let mut depay = RtpDepacketizer::new();
1668        prime_h264(&mut depay);
1669        assert!(depay
1670            .push(&rtp(&[0x7c, 0x45, 1, 2], true, 10, 99))
1671            .unwrap()
1672            .is_none());
1673    }
1674
1675    #[test]
1676    fn status_tracks_h264_decoder_config() {
1677        let mut depay = RtpDepacketizer::new();
1678        depay
1679            .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
1680            .unwrap();
1681        let status = depay.status();
1682        assert!(status.codec_config.h264_sps);
1683        assert!(!status.codec_config.h264_pps);
1684        assert!(!status.codec_config.is_complete_for(Codec::H264));
1685
1686        depay.push(&rtp(&[0x68, 0xee], true, 2, 10)).unwrap();
1687        let status = depay.status();
1688        assert!(status.codec_config.is_complete_for(Codec::H264));
1689    }
1690
1691    #[test]
1692    fn reorder_buffer_restores_short_out_of_order_burst() {
1693        let mut reorder = RtpReorderBuffer::default();
1694        let first = rtp(&[0x61, 1], true, 10, 90);
1695        let second = rtp(&[0x61, 2], true, 11, 90);
1696        let third = rtp(&[0x61, 3], true, 12, 90);
1697
1698        assert_eq!(reorder.push(&first).unwrap(), vec![first.clone()]);
1699        assert!(reorder.push(&third).unwrap().is_empty());
1700        assert_eq!(reorder.status().buffered_packets, 1);
1701        assert_eq!(reorder.status().reordered_packets, 1);
1702
1703        let ready = reorder.push(&second).unwrap();
1704        assert_eq!(ready, vec![second, third]);
1705        assert_eq!(reorder.status().buffered_packets, 0);
1706    }
1707}