Skip to main content

openipc_core/
rtp.rs

1use std::collections::{BTreeMap, VecDeque};
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/// Policy used when packet loss leaves an encoded access unit incomplete.
35///
36/// Strict applications can drop damaged units, while latency-sensitive FPV
37/// receivers can forward the bytes that arrived and let the decoder conceal
38/// the damage.
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub enum DamagedFramePolicy {
41    /// Drop access units that contain an RTP sequence gap or incomplete FU.
42    #[default]
43    Drop,
44    /// Emit the received bytes at the next known access-unit boundary.
45    Forward,
46}
47
48/// The kind of packet loss retained in a best-effort access unit.
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
50pub enum FrameDamage {
51    /// The access unit contains all packets observed by the receiver.
52    #[default]
53    None,
54    /// One or more complete packets or slices were absent between valid NAL units.
55    MissingSlice,
56    /// A fragmented NAL ends at the last contiguous byte received before a gap.
57    TruncatedFragment,
58}
59
60impl FrameDamage {
61    /// Return true when the access unit is incomplete.
62    pub const fn is_damaged(self) -> bool {
63        !matches!(self, Self::None)
64    }
65
66    const fn combine(self, other: Self) -> Self {
67        match (self, other) {
68            (Self::TruncatedFragment, _) | (_, Self::TruncatedFragment) => Self::TruncatedFragment,
69            (Self::MissingSlice, _) | (_, Self::MissingSlice) => Self::MissingSlice,
70            _ => Self::None,
71        }
72    }
73}
74
75/// Dynamic RTP payload type used by OpenIPC for H.264.
76pub const RTP_PAYLOAD_TYPE_H264: u8 = 96;
77/// Dynamic RTP payload type used by OpenIPC for H.265.
78pub const RTP_PAYLOAD_TYPE_H265: u8 = 97;
79/// Dynamic RTP payload type used by OpenIPC/Majestic for Opus audio.
80pub const RTP_PAYLOAD_TYPE_OPUS: u8 = 98;
81
82/// Decoder configuration NAL units observed by the RTP depacketizer.
83///
84/// H.264 needs SPS and PPS before a decoder can be configured. H.265 needs
85/// VPS, SPS and PPS. PixelPilot starts its decoder as soon as these parameter
86/// sets have been observed, then feeds subsequent NAL units without requiring a
87/// fresh IDR for every startup path.
88#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
89pub struct CodecConfigState {
90    /// H.264 sequence parameter set has been seen.
91    pub h264_sps: bool,
92    /// H.264 picture parameter set has been seen.
93    pub h264_pps: bool,
94    /// H.265 video parameter set has been seen.
95    pub h265_vps: bool,
96    /// H.265 sequence parameter set has been seen.
97    pub h265_sps: bool,
98    /// H.265 picture parameter set has been seen.
99    pub h265_pps: bool,
100}
101
102impl CodecConfigState {
103    /// Return true when all parameter sets required for `codec` are cached.
104    pub const fn is_complete_for(self, codec: Codec) -> bool {
105        match codec {
106            Codec::H264 => self.h264_sps && self.h264_pps,
107            Codec::H265 => self.h265_vps && self.h265_sps && self.h265_pps,
108        }
109    }
110}
111
112/// Cumulative RTP depacketizer diagnostics.
113#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
114pub struct RtpDepacketizerStatus {
115    /// RTP packets submitted to the depacketizer.
116    pub packets: u64,
117    /// Annex-B video frames emitted.
118    pub frames_emitted: u64,
119    /// Video NAL units dropped because decoder config was not complete yet.
120    pub config_wait_drops: u64,
121    /// Keyframes emitted with cached decoder config prepended.
122    pub keyframes_with_prepended_config: u64,
123    /// Cached SPS/PPS/VPS parameter-set NAL units prepended to keyframes.
124    pub parameter_sets_prepended: u64,
125    /// RTP sequence gaps observed while assembling access units.
126    pub fragment_sequence_gaps: u64,
127    /// Damaged access units forwarded to the decoder in best-effort mode.
128    pub damaged_frames_forwarded: u64,
129    /// Damaged access units discarded in strict mode.
130    pub damaged_frames_dropped: u64,
131    /// Fragment chains that exceeded the configured size guard.
132    pub fragment_overflows: u64,
133    /// Packets rejected because they were not H.264/H.265 video.
134    pub unsupported_payloads: u64,
135    /// Packets rejected because the RTP header or payload was malformed.
136    pub malformed_packets: u64,
137    /// Most recent RTP payload type.
138    pub last_payload_type: Option<u8>,
139    /// Most recent RTP sequence number.
140    pub last_sequence_number: Option<u16>,
141    /// Most recent RTP timestamp.
142    pub last_timestamp: Option<u32>,
143    /// Most recent detected video codec.
144    pub last_codec: Option<Codec>,
145    /// Most recent H.264/H.265 NAL unit type.
146    pub last_nal_type: Option<u8>,
147    /// Current decoder configuration state.
148    pub codec_config: CodecConfigState,
149}
150
151/// Parsed RTP header metadata.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct RtpHeader {
154    /// RTP marker bit, usually set at an access-unit boundary.
155    pub marker: bool,
156    /// RTP payload type.
157    pub payload_type: u8,
158    /// RTP sequence number.
159    pub sequence_number: u16,
160    /// RTP timestamp.
161    pub timestamp: u32,
162    /// RTP synchronization source.
163    pub ssrc: u32,
164    /// Number of CSRC entries.
165    pub csrc_count: u8,
166    /// True if the packet has an RTP header extension.
167    pub has_extension: bool,
168    /// Header length including CSRC and extension bytes.
169    pub header_len: usize,
170    /// Payload length after header and padding removal.
171    pub payload_len: usize,
172}
173
174impl RtpHeader {
175    /// Parse an RTP header and validate extension/padding bounds.
176    pub fn parse(packet: &[u8]) -> Result<Self, RtpError> {
177        if packet.len() < 12 {
178            return Err(RtpError::TooShort);
179        }
180        let version = packet[0] >> 6;
181        if version != 2 {
182            return Err(RtpError::InvalidVersion(version));
183        }
184
185        let padding = packet[0] & 0x20 != 0;
186        let extension = packet[0] & 0x10 != 0;
187        let csrc_count = packet[0] & 0x0f;
188        let mut header_len = 12 + csrc_count as usize * 4;
189        if packet.len() < header_len {
190            return Err(RtpError::TooShort);
191        }
192
193        if extension {
194            if packet.len() < header_len + 4 {
195                return Err(RtpError::InvalidExtension);
196            }
197            let ext_words =
198                u16::from_be_bytes([packet[header_len + 2], packet[header_len + 3]]) as usize;
199            header_len += 4 + ext_words * 4;
200            if packet.len() < header_len {
201                return Err(RtpError::InvalidExtension);
202            }
203        }
204
205        let padding_len = if padding {
206            let len = *packet.last().ok_or(RtpError::InvalidPadding)? as usize;
207            if len == 0 || len > packet.len() - header_len {
208                return Err(RtpError::InvalidPadding);
209            }
210            len
211        } else {
212            0
213        };
214
215        let payload_len = packet.len() - header_len - padding_len;
216        if payload_len == 0 {
217            return Err(RtpError::EmptyPayload);
218        }
219
220        Ok(Self {
221            marker: packet[1] & 0x80 != 0,
222            payload_type: packet[1] & 0x7f,
223            sequence_number: u16::from_be_bytes([packet[2], packet[3]]),
224            timestamp: u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]),
225            ssrc: u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
226            csrc_count,
227            has_extension: extension,
228            header_len,
229            payload_len,
230        })
231    }
232
233    /// Borrow this packet's payload using the parsed header offsets.
234    pub fn payload<'a>(&self, packet: &'a [u8]) -> &'a [u8] {
235        &packet[self.header_len..self.header_len + self.payload_len]
236    }
237}
238
239/// Cumulative status for [`RtpReorderBuffer`].
240#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
241pub struct RtpReorderStatus {
242    /// Packets currently held while waiting for missing sequence numbers.
243    pub buffered_packets: usize,
244    /// Out-of-order packets accepted into the reorder window.
245    pub reordered_packets: u64,
246    /// Packets dropped because their sequence number was older than the window.
247    pub late_packets: u64,
248    /// Times the window flushed ahead after the missing packet did not arrive.
249    pub forced_flushes: u64,
250}
251
252/// Small RTP sequence reorder buffer.
253///
254/// PixelPilot keeps a short queue before its RTP parser so FU-A/FU fragments
255/// survive small USB/radio delivery inversions. This buffer does the same for
256/// the shared Rust receiver runtime while keeping the in-order path immediate.
257#[derive(Debug, Clone)]
258pub struct RtpReorderBuffer {
259    next_sequence: Option<u16>,
260    pending: BTreeMap<u16, Vec<u8>>,
261    max_depth: usize,
262    status: RtpReorderStatus,
263}
264
265impl Default for RtpReorderBuffer {
266    fn default() -> Self {
267        Self::new(DEFAULT_RTP_REORDER_WINDOW)
268    }
269}
270
271impl RtpReorderBuffer {
272    /// Create a reorder buffer with a maximum pending packet depth.
273    pub fn new(max_depth: usize) -> Self {
274        Self {
275            next_sequence: None,
276            pending: BTreeMap::new(),
277            max_depth: max_depth.max(1),
278            status: RtpReorderStatus::default(),
279        }
280    }
281
282    /// Push one RTP packet and return packets that are ready in sequence order.
283    pub fn push(&mut self, packet: &[u8]) -> Result<Vec<Vec<u8>>, RtpError> {
284        let header = RtpHeader::parse(packet)?;
285        let sequence = header.sequence_number;
286        let mut ready = Vec::new();
287
288        let Some(expected) = self.next_sequence else {
289            self.next_sequence = Some(sequence.wrapping_add(1));
290            ready.push(packet.to_vec());
291            return Ok(ready);
292        };
293
294        if sequence == expected {
295            self.next_sequence = Some(expected.wrapping_add(1));
296            ready.push(packet.to_vec());
297            self.drain_ready(&mut ready);
298            return Ok(ready);
299        }
300
301        if sequence_is_before(sequence, expected) {
302            self.status.late_packets = self.status.late_packets.saturating_add(1);
303            return Ok(ready);
304        }
305
306        if self.pending.insert(sequence, packet.to_vec()).is_none() {
307            self.status.reordered_packets = self.status.reordered_packets.saturating_add(1);
308        }
309        if self.pending.len() >= self.max_depth {
310            self.force_flush(expected, &mut ready);
311        }
312        self.status.buffered_packets = self.pending.len();
313        Ok(ready)
314    }
315
316    /// Return current reorder-buffer status.
317    pub fn status(&self) -> RtpReorderStatus {
318        RtpReorderStatus {
319            buffered_packets: self.pending.len(),
320            ..self.status
321        }
322    }
323
324    fn drain_ready(&mut self, ready: &mut Vec<Vec<u8>>) {
325        while let Some(expected) = self.next_sequence {
326            let Some(packet) = self.pending.remove(&expected) else {
327                break;
328            };
329            self.next_sequence = Some(expected.wrapping_add(1));
330            ready.push(packet);
331        }
332        self.status.buffered_packets = self.pending.len();
333    }
334
335    fn force_flush(&mut self, expected: u16, ready: &mut Vec<Vec<u8>>) {
336        let Some(sequence) = self
337            .pending
338            .keys()
339            .copied()
340            .min_by_key(|sequence| sequence.wrapping_sub(expected))
341        else {
342            return;
343        };
344        if let Some(packet) = self.pending.remove(&sequence) {
345            self.status.forced_flushes = self.status.forced_flushes.saturating_add(1);
346            self.next_sequence = Some(sequence.wrapping_add(1));
347            ready.push(packet);
348            self.drain_ready(ready);
349        }
350    }
351}
352
353fn sequence_is_before(sequence: u16, expected: u16) -> bool {
354    let backward = expected.wrapping_sub(sequence);
355    backward != 0 && backward < 0x8000
356}
357
358/// Encoded Annex-B H.264/H.265 access unit emitted by [`RtpDepacketizer`].
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct DepacketizedFrame {
361    /// Annex-B byte stream data including start codes.
362    pub data: Vec<u8>,
363    /// RTP timestamp associated with the access unit.
364    pub timestamp: u32,
365    /// True when the frame contains an IDR/keyframe entry point.
366    pub is_keyframe: bool,
367    /// Video codec for this frame.
368    pub codec: Codec,
369    /// RTP payload type that produced this frame.
370    pub payload_type: u8,
371    /// RTP sequence number of the packet that completed this frame.
372    pub sequence_number: u16,
373    /// H.264/H.265 NAL unit type for the frame payload.
374    pub nal_type: u8,
375    /// Decoder parameter-set state at the time this frame was emitted.
376    pub codec_config: CodecConfigState,
377    /// True when packet loss left this access unit incomplete.
378    pub damaged: bool,
379    /// Classification of the retained packet damage.
380    pub damage: FrameDamage,
381}
382
383#[derive(Debug, Default, Clone)]
384struct FragmentState {
385    data: Vec<u8>,
386    timestamp: u32,
387    next_sequence: Option<u16>,
388    corrupted: bool,
389    is_keyframe: bool,
390    payload_type: u8,
391    sequence_number: u16,
392    nal_type: u8,
393}
394
395#[derive(Debug, Default, Clone)]
396struct AccessUnitState {
397    data: Vec<u8>,
398    timestamp: Option<u32>,
399    next_sequence: Option<u16>,
400    corrupted: bool,
401    damage: FrameDamage,
402    is_keyframe: bool,
403    has_decoder_config: bool,
404    payload_type: u8,
405    sequence_number: u16,
406    nal_type: u8,
407}
408
409#[derive(Debug, Clone, Copy)]
410struct FrameMeta {
411    timestamp: u32,
412    is_keyframe: bool,
413    codec: Codec,
414    payload_type: u8,
415    sequence_number: u16,
416    nal_type: u8,
417    damage: FrameDamage,
418}
419
420/// Stateful RTP depacketizer for OpenIPC H.264/H.265 video.
421///
422/// The depacketizer buffers fragmented NAL units and emits Annex-B access
423/// units. Its damaged-frame policy controls whether incomplete units are
424/// dropped or forwarded for decoder error concealment.
425#[derive(Debug, Clone)]
426pub struct RtpDepacketizer {
427    h264: FragmentState,
428    h265: FragmentState,
429    h264_access_unit: AccessUnitState,
430    h265_access_unit: AccessUnitState,
431    h264_sps: Option<Vec<u8>>,
432    h264_pps: Option<Vec<u8>>,
433    h265_vps: Option<Vec<u8>>,
434    h265_sps: Option<Vec<u8>>,
435    h265_pps: Option<Vec<u8>>,
436    codec_hint: Option<Codec>,
437    detected_codec: Option<Codec>,
438    h265_legacy_fu: bool,
439    damaged_frame_policy: DamagedFramePolicy,
440    ready_frames: VecDeque<DepacketizedFrame>,
441    max_fragment: usize,
442    status: RtpDepacketizerStatus,
443}
444
445impl Default for RtpDepacketizer {
446    fn default() -> Self {
447        Self::new()
448    }
449}
450
451impl RtpDepacketizer {
452    /// Create a depacketizer with the default fragment-size guard.
453    pub fn new() -> Self {
454        Self {
455            h264: FragmentState::default(),
456            h265: FragmentState::default(),
457            h264_access_unit: AccessUnitState::default(),
458            h265_access_unit: AccessUnitState::default(),
459            h264_sps: None,
460            h264_pps: None,
461            h265_vps: None,
462            h265_sps: None,
463            h265_pps: None,
464            codec_hint: None,
465            detected_codec: None,
466            h265_legacy_fu: false,
467            damaged_frame_policy: DamagedFramePolicy::Drop,
468            ready_frames: VecDeque::new(),
469            max_fragment: DEFAULT_MAX_ACCESS_UNIT_SIZE,
470            status: RtpDepacketizerStatus::default(),
471        }
472    }
473
474    /// Return cumulative depacketizer status and codec configuration state.
475    pub fn status(&self) -> RtpDepacketizerStatus {
476        RtpDepacketizerStatus {
477            codec_config: self.codec_config(),
478            ..self.status
479        }
480    }
481
482    /// Return the current decoder parameter-set state.
483    pub fn codec_config(&self) -> CodecConfigState {
484        CodecConfigState {
485            h264_sps: self.h264_sps.is_some(),
486            h264_pps: self.h264_pps.is_some(),
487            h265_vps: self.h265_vps.is_some(),
488            h265_sps: self.h265_sps.is_some(),
489            h265_pps: self.h265_pps.is_some(),
490        }
491    }
492
493    /// Prefer a codec when a transmitter reuses the same dynamic RTP payload
494    /// type for H.264 and H.265.
495    ///
496    /// `None` keeps automatic detection. Automatic mode locks from unambiguous
497    /// SPS/PPS/VPS NAL units, so it also accepts Divinus H.265 on payload type
498    /// 96. A hint is useful when joining a stream after its parameter sets.
499    pub fn set_codec_hint(&mut self, codec: Option<Codec>) {
500        self.codec_hint = codec;
501        self.detected_codec = codec;
502    }
503
504    /// Choose whether packet-damaged access units are dropped or forwarded.
505    ///
506    /// The default is [`DamagedFramePolicy::Drop`]. FPV applications normally
507    /// prefer [`DamagedFramePolicy::Forward`] so a decoder can conceal damage
508    /// instead of waiting for an entirely clean picture.
509    pub fn set_damaged_frame_policy(&mut self, policy: DamagedFramePolicy) {
510        self.damaged_frame_policy = policy;
511    }
512
513    /// Return the active damaged-frame policy.
514    pub const fn damaged_frame_policy(&self) -> DamagedFramePolicy {
515        self.damaged_frame_policy
516    }
517
518    /// Push one RTP packet and return a complete frame when one is ready.
519    pub fn push(&mut self, packet: &[u8]) -> Result<Option<DepacketizedFrame>, RtpError> {
520        self.status.packets = self.status.packets.saturating_add(1);
521        let header = match RtpHeader::parse(packet) {
522            Ok(header) => header,
523            Err(err) => {
524                self.record_error(err);
525                return Err(err);
526            }
527        };
528        self.status.last_payload_type = Some(header.payload_type);
529        self.status.last_sequence_number = Some(header.sequence_number);
530        self.status.last_timestamp = Some(header.timestamp);
531        let rtp_payload = header.payload(packet);
532        let mut annex_b = AnnexBNalus::new(rtp_payload);
533        let first_annex_b = annex_b.next();
534        let payload = first_annex_b.unwrap_or(rtp_payload);
535        if payload.is_empty() {
536            self.record_error(RtpError::EmptyPayload);
537            return Err(RtpError::EmptyPayload);
538        }
539        log::trace!(
540            target: "openipc_core::rtp",
541            "received RTP packet sequence={} timestamp={} pt={} marker={} bytes={}",
542            header.sequence_number,
543            header.timestamp,
544            header.payload_type,
545            header.marker,
546            payload.len()
547        );
548        if header.payload_type == RTP_PAYLOAD_TYPE_OPUS {
549            self.record_error(RtpError::UnsupportedPayload);
550            return Err(RtpError::UnsupportedPayload);
551        }
552        let Some(codec) = self.resolve_codec(header.payload_type, payload) else {
553            self.record_error(RtpError::UnsupportedPayload);
554            return Err(RtpError::UnsupportedPayload);
555        };
556        self.status.last_codec = Some(codec);
557        if let Some(frame) = self.flush_incomplete_fragment_on_timestamp(codec, header.timestamp)? {
558            self.ready_frames.push_back(frame);
559        }
560        if let Some(frame) = self.observe_access_unit_packet(codec, header) {
561            self.ready_frames.push_back(frame);
562        }
563        if first_annex_b.is_some() {
564            let mut current = payload;
565            loop {
566                let next = annex_b.next();
567                let mut unit_header = header;
568                unit_header.marker = header.marker && next.is_none();
569                self.push_codec_payload(codec, current, unit_header)?;
570                let Some(next) = next else {
571                    break;
572                };
573                current = next;
574            }
575        } else {
576            self.push_codec_payload(codec, payload, header)?;
577        }
578        let frame = self.ready_frames.pop_front();
579        if frame.is_some() {
580            self.status.frames_emitted = self.status.frames_emitted.saturating_add(1);
581        }
582        Ok(frame)
583    }
584
585    fn push_codec_payload(
586        &mut self,
587        codec: Codec,
588        payload: &[u8],
589        header: RtpHeader,
590    ) -> Result<(), RtpError> {
591        let result = match codec {
592            Codec::H264 => self.push_h264(payload, header),
593            Codec::H265 => self.push_h265(payload, header),
594        };
595        match result {
596            Ok(Some(frame)) => self.ready_frames.push_back(frame),
597            Err(err) => {
598                log::debug!(
599                    target: "openipc_core::rtp",
600                    "RTP packet rejected sequence={}: {err:?}",
601                    header.sequence_number
602                );
603                self.record_error(err);
604                return Err(err);
605            }
606            _ => {}
607        }
608        Ok(())
609    }
610
611    fn resolve_codec(&mut self, payload_type: u8, payload: &[u8]) -> Option<Codec> {
612        if let Some(codec) = self.codec_hint {
613            return Some(codec);
614        }
615        if let Some(codec) = self.detected_codec {
616            return Some(codec);
617        }
618        if let Some(codec) = codec_parameter_set_signature(payload) {
619            self.detected_codec = Some(codec);
620            return Some(codec);
621        }
622        let codec = codec_from_payload_type(payload_type).or_else(|| detect_codec(payload));
623        if payload_type == RTP_PAYLOAD_TYPE_H265 {
624            self.detected_codec = codec;
625        }
626        codec
627    }
628
629    fn push_h264(
630        &mut self,
631        payload: &[u8],
632        header: RtpHeader,
633    ) -> Result<Option<DepacketizedFrame>, RtpError> {
634        let nal_type = payload[0] & 0x1f;
635        self.status.last_nal_type = Some(nal_type);
636        match nal_type {
637            7 => {
638                self.h264_sps = Some(payload.to_vec());
639                Ok(None)
640            }
641            8 => {
642                self.h264_pps = Some(payload.to_vec());
643                Ok(None)
644            }
645            24 => self.h264_stap_a(payload, header),
646            28 => self.h264_fu_a(payload, header),
647            _ if self.has_decoder_config(Codec::H264) && is_h264_vcl_nal(nal_type) => self
648                .push_complete_nalu(
649                    payload,
650                    FrameMeta {
651                        timestamp: header.timestamp,
652                        is_keyframe: nal_type == 5,
653                        codec: Codec::H264,
654                        payload_type: header.payload_type,
655                        sequence_number: header.sequence_number,
656                        nal_type,
657                        damage: FrameDamage::None,
658                    },
659                    header.marker,
660                ),
661            _ if !is_h264_vcl_nal(nal_type) => Ok(None),
662            _ => {
663                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
664                Ok(None)
665            }
666        }
667    }
668
669    fn push_h265(
670        &mut self,
671        payload: &[u8],
672        header: RtpHeader,
673    ) -> Result<Option<DepacketizedFrame>, RtpError> {
674        if payload.len() < 2 {
675            return Err(RtpError::UnsupportedPayload);
676        }
677        let nal_type = (payload[0] >> 1) & 0x3f;
678        self.status.last_nal_type = Some(nal_type);
679        match nal_type {
680            32 => {
681                self.h265_vps = Some(payload.to_vec());
682                Ok(None)
683            }
684            33 => {
685                self.h265_sps = Some(payload.to_vec());
686                Ok(None)
687            }
688            34 => {
689                self.h265_pps = Some(payload.to_vec());
690                Ok(None)
691            }
692            48 => self.h265_ap(payload, header),
693            49 => self.h265_fu(payload, header),
694            _ if self.has_decoder_config(Codec::H265) && is_h265_vcl_nal(nal_type) => self
695                .push_complete_nalu(
696                    payload,
697                    FrameMeta {
698                        timestamp: header.timestamp,
699                        is_keyframe: (16..=23).contains(&nal_type),
700                        codec: Codec::H265,
701                        payload_type: header.payload_type,
702                        sequence_number: header.sequence_number,
703                        nal_type,
704                        damage: FrameDamage::None,
705                    },
706                    header.marker,
707                ),
708            _ if !is_h265_vcl_nal(nal_type) => Ok(None),
709            _ => {
710                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
711                Ok(None)
712            }
713        }
714    }
715
716    fn h264_fu_a(
717        &mut self,
718        payload: &[u8],
719        header: RtpHeader,
720    ) -> Result<Option<DepacketizedFrame>, RtpError> {
721        if payload.len() < 2 {
722            return Err(RtpError::UnsupportedPayload);
723        }
724        let fu_indicator = payload[0];
725        let fu_header = payload[1];
726        let start = fu_header & 0x80 != 0;
727        let end = fu_header & 0x40 != 0;
728        let nal_type = fu_header & 0x1f;
729        if start {
730            self.h264.data.clear();
731            self.h264.data.extend_from_slice(&[0, 0, 0, 1]);
732            self.h264.timestamp = header.timestamp;
733            self.h264.next_sequence = Some(header.sequence_number.wrapping_add(1));
734            self.h264.corrupted = false;
735            self.h264.is_keyframe = nal_type == 5;
736            self.h264.payload_type = header.payload_type;
737            self.h264.sequence_number = header.sequence_number;
738            self.h264.nal_type = nal_type;
739            self.h264.data.push((fu_indicator & 0xe0) | nal_type);
740        }
741        let append_payload =
742            start || self.accept_fragment_sequence(Codec::H264, header.sequence_number);
743        self.h264.sequence_number = header.sequence_number;
744        if append_payload {
745            self.append_fragment(Codec::H264, &payload[2..])?;
746        }
747        if end {
748            if self.h264.data.is_empty() {
749                self.reset_fragment(Codec::H264);
750                return Ok(None);
751            }
752            if !is_h264_vcl_nal(nal_type) {
753                self.reset_fragment(Codec::H264);
754                return Ok(None);
755            }
756            if (self.h264.corrupted && self.damaged_frame_policy == DamagedFramePolicy::Drop)
757                || !self.has_decoder_config(Codec::H264)
758            {
759                if !self.has_decoder_config(Codec::H264) {
760                    self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
761                }
762                self.reset_fragment(Codec::H264);
763                return Ok(None);
764            }
765            let data = std::mem::take(&mut self.h264.data);
766            let meta = FrameMeta {
767                timestamp: self.h264.timestamp,
768                is_keyframe: nal_type == 5,
769                codec: Codec::H264,
770                payload_type: header.payload_type,
771                sequence_number: header.sequence_number,
772                nal_type,
773                damage: if self.h264.corrupted {
774                    FrameDamage::TruncatedFragment
775                } else {
776                    FrameDamage::None
777                },
778            };
779            self.reset_fragment(Codec::H264);
780            self.push_complete_owned_annex_b(data, meta, header.marker, false)
781        } else {
782            Ok(None)
783        }
784    }
785
786    fn h265_fu(
787        &mut self,
788        payload: &[u8],
789        header: RtpHeader,
790    ) -> Result<Option<DepacketizedFrame>, RtpError> {
791        if payload.len() < 3 {
792            return Err(RtpError::UnsupportedPayload);
793        }
794        // Divinus's special UDP output predates its standards-compliant RTSP
795        // packetizer. It emits [FU indicator, FU header, data] and stores the
796        // original first HEVC header byte in the FU type field. Keep support
797        // local to an FU chain; normal RFC 7798 packets remain unchanged.
798        let legacy_start = payload[1] & 0x80 != 0;
799        if legacy_start {
800            self.h265_legacy_fu = true;
801        }
802        let legacy = self.h265_legacy_fu;
803        let fu_header = payload[if legacy { 1 } else { 2 }];
804        let start = fu_header & 0x80 != 0;
805        let end = fu_header & 0x40 != 0;
806        let nal_type = if legacy {
807            (fu_header & 0x3f) >> 1
808        } else {
809            fu_header & 0x3f
810        };
811        if start {
812            self.h265.data.clear();
813            self.h265.data.extend_from_slice(&[0, 0, 0, 1]);
814            self.h265.timestamp = header.timestamp;
815            self.h265.next_sequence = Some(header.sequence_number.wrapping_add(1));
816            self.h265.corrupted = false;
817            self.h265.is_keyframe = (16..=23).contains(&nal_type);
818            self.h265.payload_type = header.payload_type;
819            self.h265.sequence_number = header.sequence_number;
820            self.h265.nal_type = nal_type;
821            if legacy {
822                self.h265
823                    .data
824                    .push((payload[0] & 0x80) | (fu_header & 0x3f));
825                // Divinus omits the second HEVC payload-header byte. OpenIPC
826                // encoder output uses layer 0 and temporal_id_plus1 1.
827                self.h265.data.push(0x01);
828            } else {
829                self.h265.data.push((payload[0] & 0x81) | (nal_type << 1));
830                self.h265.data.push(payload[1]);
831            }
832        }
833        let append_payload =
834            start || self.accept_fragment_sequence(Codec::H265, header.sequence_number);
835        self.h265.sequence_number = header.sequence_number;
836        if append_payload {
837            self.append_fragment(Codec::H265, &payload[if legacy { 2 } else { 3 }..])?;
838        }
839        if end {
840            if self.h265.data.is_empty() {
841                self.reset_fragment(Codec::H265);
842                self.h265_legacy_fu = false;
843                return Ok(None);
844            }
845            if !is_h265_vcl_nal(nal_type) {
846                self.reset_fragment(Codec::H265);
847                self.h265_legacy_fu = false;
848                return Ok(None);
849            }
850            if (self.h265.corrupted && self.damaged_frame_policy == DamagedFramePolicy::Drop)
851                || !self.has_decoder_config(Codec::H265)
852            {
853                if !self.has_decoder_config(Codec::H265) {
854                    self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
855                }
856                self.reset_fragment(Codec::H265);
857                self.h265_legacy_fu = false;
858                return Ok(None);
859            }
860            let data = std::mem::take(&mut self.h265.data);
861            let meta = FrameMeta {
862                timestamp: self.h265.timestamp,
863                is_keyframe: (16..=23).contains(&nal_type),
864                codec: Codec::H265,
865                payload_type: header.payload_type,
866                sequence_number: header.sequence_number,
867                nal_type,
868                damage: if self.h265.corrupted {
869                    FrameDamage::TruncatedFragment
870                } else {
871                    FrameDamage::None
872                },
873            };
874            self.reset_fragment(Codec::H265);
875            self.h265_legacy_fu = false;
876            self.push_complete_owned_annex_b(data, meta, header.marker, false)
877        } else {
878            Ok(None)
879        }
880    }
881
882    fn accept_fragment_sequence(&mut self, codec: Codec, sequence_number: u16) -> bool {
883        let state = match codec {
884            Codec::H264 => &mut self.h264,
885            Codec::H265 => &mut self.h265,
886        };
887        let Some(expected) = state.next_sequence else {
888            return false;
889        };
890        state.next_sequence = Some(sequence_number.wrapping_add(1));
891        if sequence_number != expected {
892            state.corrupted = true;
893            if self.damaged_frame_policy == DamagedFramePolicy::Drop {
894                state.data.clear();
895                self.status.damaged_frames_dropped =
896                    self.status.damaged_frames_dropped.saturating_add(1);
897            }
898            return false;
899        }
900        !state.corrupted
901    }
902
903    fn reset_fragment(&mut self, codec: Codec) {
904        let state = match codec {
905            Codec::H264 => &mut self.h264,
906            Codec::H265 => &mut self.h265,
907        };
908        state.data.clear();
909        state.next_sequence = None;
910        state.corrupted = false;
911        state.is_keyframe = false;
912        state.payload_type = 0;
913        state.sequence_number = 0;
914        state.nal_type = 0;
915    }
916
917    fn h264_stap_a(
918        &mut self,
919        payload: &[u8],
920        header: RtpHeader,
921    ) -> Result<Option<DepacketizedFrame>, RtpError> {
922        let mut out = Vec::new();
923        let mut offset = 1;
924        let mut keyframe = false;
925        let mut has_slice = false;
926        let mut has_sps = false;
927        let mut has_pps = false;
928        let mut last_slice_type = 0;
929        while offset + 2 <= payload.len() {
930            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
931            offset += 2;
932            if len == 0 || offset.saturating_add(len) > payload.len() {
933                return Err(RtpError::UnsupportedPayload);
934            }
935            let nalu = &payload[offset..offset + len];
936            let nal_type = nalu.first().map(|b| b & 0x1f).unwrap_or(0);
937            self.status.last_nal_type = Some(nal_type);
938            match nal_type {
939                7 => {
940                    has_sps = true;
941                    self.h264_sps = Some(nalu.to_vec());
942                }
943                8 => {
944                    has_pps = true;
945                    self.h264_pps = Some(nalu.to_vec());
946                }
947                _ => {}
948            }
949            if is_h264_vcl_nal(nal_type) {
950                has_slice = true;
951                keyframe |= nal_type == 5;
952                last_slice_type = nal_type;
953            }
954            append_annex_b(&mut out, nalu);
955            offset += len;
956        }
957        if offset != payload.len() {
958            return Err(RtpError::UnsupportedPayload);
959        }
960        if !has_slice || !self.has_decoder_config(Codec::H264) {
961            if has_slice {
962                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
963            }
964            return Ok(None);
965        }
966        self.push_complete_owned_annex_b(
967            out,
968            FrameMeta {
969                timestamp: header.timestamp,
970                is_keyframe: keyframe,
971                codec: Codec::H264,
972                payload_type: header.payload_type,
973                sequence_number: header.sequence_number,
974                nal_type: last_slice_type,
975                damage: FrameDamage::None,
976            },
977            header.marker,
978            has_sps && has_pps,
979        )
980    }
981
982    fn h265_ap(
983        &mut self,
984        payload: &[u8],
985        header: RtpHeader,
986    ) -> Result<Option<DepacketizedFrame>, RtpError> {
987        let mut out = Vec::new();
988        let mut offset = 2;
989        let mut keyframe = false;
990        let mut has_slice = false;
991        let mut has_vps = false;
992        let mut has_sps = false;
993        let mut has_pps = false;
994        let mut last_slice_type = 0;
995        while offset + 2 <= payload.len() {
996            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
997            offset += 2;
998            if len == 0 || offset.saturating_add(len) > payload.len() {
999                return Err(RtpError::UnsupportedPayload);
1000            }
1001            let nalu = &payload[offset..offset + len];
1002            let nal_type = nalu.first().map(|b| (b >> 1) & 0x3f).unwrap_or(0);
1003            self.status.last_nal_type = Some(nal_type);
1004            match nal_type {
1005                32 => {
1006                    has_vps = true;
1007                    self.h265_vps = Some(nalu.to_vec());
1008                }
1009                33 => {
1010                    has_sps = true;
1011                    self.h265_sps = Some(nalu.to_vec());
1012                }
1013                34 => {
1014                    has_pps = true;
1015                    self.h265_pps = Some(nalu.to_vec());
1016                }
1017                _ => {}
1018            }
1019            if is_h265_vcl_nal(nal_type) {
1020                has_slice = true;
1021                keyframe |= (16..=23).contains(&nal_type);
1022                last_slice_type = nal_type;
1023            }
1024            append_annex_b(&mut out, nalu);
1025            offset += len;
1026        }
1027        if offset != payload.len() {
1028            return Err(RtpError::UnsupportedPayload);
1029        }
1030        if !has_slice || !self.has_decoder_config(Codec::H265) {
1031            if has_slice {
1032                self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
1033            }
1034            return Ok(None);
1035        }
1036        self.push_complete_owned_annex_b(
1037            out,
1038            FrameMeta {
1039                timestamp: header.timestamp,
1040                is_keyframe: keyframe,
1041                codec: Codec::H265,
1042                payload_type: header.payload_type,
1043                sequence_number: header.sequence_number,
1044                nal_type: last_slice_type,
1045                damage: FrameDamage::None,
1046            },
1047            header.marker,
1048            has_vps && has_sps && has_pps,
1049        )
1050    }
1051
1052    fn append_fragment(&mut self, codec: Codec, bytes: &[u8]) -> Result<(), RtpError> {
1053        let state = match codec {
1054            Codec::H264 => &mut self.h264,
1055            Codec::H265 => &mut self.h265,
1056        };
1057        if state.data.len() + bytes.len() > self.max_fragment {
1058            self.status.fragment_overflows = self.status.fragment_overflows.saturating_add(1);
1059            return Err(RtpError::FragmentOverflow);
1060        }
1061        state.data.extend_from_slice(bytes);
1062        Ok(())
1063    }
1064
1065    fn push_complete_nalu(
1066        &mut self,
1067        nalu: &[u8],
1068        meta: FrameMeta,
1069        marker: bool,
1070    ) -> Result<Option<DepacketizedFrame>, RtpError> {
1071        let annex_b_len = nalu.len().saturating_add(4);
1072        if !self.prepare_access_unit_append(meta, marker, annex_b_len)? {
1073            return Ok(None);
1074        }
1075        let state = self.access_unit_mut(meta.codec);
1076        state.data.extend_from_slice(&[0, 0, 0, 1]);
1077        state.data.extend_from_slice(nalu);
1078        self.finish_access_unit(meta, marker, false)
1079    }
1080
1081    fn push_complete_owned_annex_b(
1082        &mut self,
1083        annex_b: Vec<u8>,
1084        meta: FrameMeta,
1085        marker: bool,
1086        has_decoder_config: bool,
1087    ) -> Result<Option<DepacketizedFrame>, RtpError> {
1088        if !self.prepare_access_unit_append(meta, marker, annex_b.len())? {
1089            return Ok(None);
1090        }
1091        let state = self.access_unit_mut(meta.codec);
1092        if state.data.is_empty() {
1093            state.data = annex_b;
1094        } else {
1095            state.data.extend_from_slice(&annex_b);
1096        }
1097        self.finish_access_unit(meta, marker, has_decoder_config)
1098    }
1099
1100    fn prepare_access_unit_append(
1101        &mut self,
1102        meta: FrameMeta,
1103        marker: bool,
1104        additional_len: usize,
1105    ) -> Result<bool, RtpError> {
1106        let max_fragment = self.max_fragment;
1107        let drop_damaged = self.damaged_frame_policy == DamagedFramePolicy::Drop;
1108        let state = self.access_unit_mut(meta.codec);
1109        debug_assert_eq!(state.timestamp, Some(meta.timestamp));
1110        if state.corrupted && drop_damaged {
1111            if marker {
1112                reset_access_unit_state(state);
1113            }
1114            return Ok(false);
1115        }
1116        if state.data.len().saturating_add(additional_len) > max_fragment {
1117            reset_access_unit_state(state);
1118            self.status.fragment_overflows = self.status.fragment_overflows.saturating_add(1);
1119            return Err(RtpError::FragmentOverflow);
1120        }
1121        state.data.reserve(additional_len);
1122        Ok(true)
1123    }
1124
1125    fn finish_access_unit(
1126        &mut self,
1127        meta: FrameMeta,
1128        marker: bool,
1129        has_decoder_config: bool,
1130    ) -> Result<Option<DepacketizedFrame>, RtpError> {
1131        let state = self.access_unit_mut(meta.codec);
1132        state.damage = state.damage.combine(meta.damage);
1133        state.corrupted = state.damage.is_damaged();
1134        state.is_keyframe |= meta.is_keyframe;
1135        state.has_decoder_config |= has_decoder_config;
1136        state.payload_type = meta.payload_type;
1137        state.sequence_number = meta.sequence_number;
1138        state.nal_type = meta.nal_type;
1139        if !marker {
1140            return Ok(None);
1141        }
1142
1143        let mut data = std::mem::take(&mut state.data);
1144        let is_keyframe = state.is_keyframe;
1145        let has_decoder_config = state.has_decoder_config;
1146        let nal_type = state.nal_type;
1147        let damage = state.damage;
1148        let damaged = damage.is_damaged();
1149        reset_access_unit_state(state);
1150        if damaged && self.damaged_frame_policy == DamagedFramePolicy::Drop {
1151            self.status.damaged_frames_dropped =
1152                self.status.damaged_frames_dropped.saturating_add(1);
1153            return Ok(None);
1154        }
1155        if damaged {
1156            self.status.damaged_frames_forwarded =
1157                self.status.damaged_frames_forwarded.saturating_add(1);
1158        }
1159        if is_keyframe && !has_decoder_config {
1160            let mut prefixed = Vec::with_capacity(data.len() + self.cached_config_len(meta.codec));
1161            self.prepend_cached_config(&mut prefixed, meta.codec);
1162            prefixed.append(&mut data);
1163            data = prefixed;
1164        }
1165        Ok(Some(DepacketizedFrame {
1166            data,
1167            timestamp: meta.timestamp,
1168            is_keyframe,
1169            codec: meta.codec,
1170            payload_type: meta.payload_type,
1171            sequence_number: meta.sequence_number,
1172            nal_type,
1173            codec_config: self.codec_config(),
1174            damaged,
1175            damage,
1176        }))
1177    }
1178
1179    fn access_unit_mut(&mut self, codec: Codec) -> &mut AccessUnitState {
1180        match codec {
1181            Codec::H264 => &mut self.h264_access_unit,
1182            Codec::H265 => &mut self.h265_access_unit,
1183        }
1184    }
1185
1186    fn observe_access_unit_packet(
1187        &mut self,
1188        codec: Codec,
1189        header: RtpHeader,
1190    ) -> Option<DepacketizedFrame> {
1191        let timestamp_changed = self
1192            .access_unit_mut(codec)
1193            .timestamp
1194            .is_some_and(|timestamp| timestamp != header.timestamp);
1195        let completed = timestamp_changed
1196            .then(|| self.take_unmarked_access_unit(codec))
1197            .flatten();
1198        let drop_damaged = self.damaged_frame_policy == DamagedFramePolicy::Drop;
1199        let (sequence_gap, discarded_data) = {
1200            let state = self.access_unit_mut(codec);
1201            if state.timestamp.is_none() {
1202                state.timestamp = Some(header.timestamp);
1203            }
1204            let sequence_gap = state
1205                .next_sequence
1206                .is_some_and(|expected| expected != header.sequence_number);
1207            let discarded_data = sequence_gap && drop_damaged && !state.data.is_empty();
1208            if sequence_gap {
1209                state.corrupted = true;
1210                state.damage = state.damage.combine(FrameDamage::MissingSlice);
1211                if drop_damaged {
1212                    state.data.clear();
1213                }
1214            }
1215            state.next_sequence = Some(header.sequence_number.wrapping_add(1));
1216            (sequence_gap, discarded_data)
1217        };
1218        if sequence_gap {
1219            self.status.fragment_sequence_gaps =
1220                self.status.fragment_sequence_gaps.saturating_add(1);
1221            if discarded_data {
1222                self.status.damaged_frames_dropped =
1223                    self.status.damaged_frames_dropped.saturating_add(1);
1224            }
1225        }
1226        completed
1227    }
1228
1229    fn take_unmarked_access_unit(&mut self, codec: Codec) -> Option<DepacketizedFrame> {
1230        let state = self.access_unit_mut(codec);
1231        if state.data.is_empty() {
1232            reset_access_unit_state(state);
1233            return None;
1234        }
1235        let mut data = std::mem::take(&mut state.data);
1236        let timestamp = state.timestamp.unwrap_or_default();
1237        let is_keyframe = state.is_keyframe;
1238        let has_decoder_config = state.has_decoder_config;
1239        let payload_type = state.payload_type;
1240        let sequence_number = state.sequence_number;
1241        let nal_type = state.nal_type;
1242        let damage = state.damage;
1243        let damaged = damage.is_damaged();
1244        reset_access_unit_state(state);
1245        if damaged && self.damaged_frame_policy == DamagedFramePolicy::Drop {
1246            self.status.damaged_frames_dropped =
1247                self.status.damaged_frames_dropped.saturating_add(1);
1248            return None;
1249        }
1250        if damaged {
1251            self.status.damaged_frames_forwarded =
1252                self.status.damaged_frames_forwarded.saturating_add(1);
1253        }
1254        if is_keyframe && !has_decoder_config {
1255            let mut prefixed = Vec::with_capacity(data.len() + self.cached_config_len(codec));
1256            self.prepend_cached_config(&mut prefixed, codec);
1257            prefixed.append(&mut data);
1258            data = prefixed;
1259        }
1260        Some(DepacketizedFrame {
1261            data,
1262            timestamp,
1263            is_keyframe,
1264            codec,
1265            payload_type,
1266            sequence_number,
1267            nal_type,
1268            codec_config: self.codec_config(),
1269            damaged,
1270            damage,
1271        })
1272    }
1273
1274    fn flush_incomplete_fragment_on_timestamp(
1275        &mut self,
1276        codec: Codec,
1277        next_timestamp: u32,
1278    ) -> Result<Option<DepacketizedFrame>, RtpError> {
1279        let should_flush = {
1280            let state = match codec {
1281                Codec::H264 => &self.h264,
1282                Codec::H265 => &self.h265,
1283            };
1284            state.next_sequence.is_some()
1285                && !state.data.is_empty()
1286                && state.timestamp != next_timestamp
1287        };
1288        if !should_flush {
1289            return Ok(None);
1290        }
1291
1292        if !self.has_decoder_config(codec) {
1293            self.reset_fragment(codec);
1294            if codec == Codec::H265 {
1295                self.h265_legacy_fu = false;
1296            }
1297            self.status.config_wait_drops = self.status.config_wait_drops.saturating_add(1);
1298            self.status.damaged_frames_dropped =
1299                self.status.damaged_frames_dropped.saturating_add(1);
1300            return Ok(None);
1301        }
1302
1303        if self.damaged_frame_policy == DamagedFramePolicy::Drop {
1304            self.reset_fragment(codec);
1305            if codec == Codec::H265 {
1306                self.h265_legacy_fu = false;
1307            }
1308            self.status.damaged_frames_dropped =
1309                self.status.damaged_frames_dropped.saturating_add(1);
1310            return Ok(None);
1311        }
1312
1313        let (data, meta) = {
1314            let state = match codec {
1315                Codec::H264 => &mut self.h264,
1316                Codec::H265 => &mut self.h265,
1317            };
1318            let data = std::mem::take(&mut state.data);
1319            let meta = FrameMeta {
1320                timestamp: state.timestamp,
1321                is_keyframe: state.is_keyframe,
1322                codec,
1323                payload_type: state.payload_type,
1324                sequence_number: state.sequence_number,
1325                nal_type: state.nal_type,
1326                damage: FrameDamage::TruncatedFragment,
1327            };
1328            (data, meta)
1329        };
1330        self.reset_fragment(codec);
1331        if codec == Codec::H265 {
1332            self.h265_legacy_fu = false;
1333        }
1334        self.push_complete_owned_annex_b(data, meta, true, false)
1335    }
1336
1337    fn cached_config_len(&self, codec: Codec) -> usize {
1338        match codec {
1339            Codec::H264 => {
1340                self.h264_sps.as_ref().map_or(0, Vec::len)
1341                    + self.h264_pps.as_ref().map_or(0, Vec::len)
1342                    + 8
1343            }
1344            Codec::H265 => {
1345                self.h265_vps.as_ref().map_or(0, Vec::len)
1346                    + self.h265_sps.as_ref().map_or(0, Vec::len)
1347                    + self.h265_pps.as_ref().map_or(0, Vec::len)
1348                    + 12
1349            }
1350        }
1351    }
1352
1353    fn prepend_cached_config(&mut self, data: &mut Vec<u8>, codec: Codec) {
1354        let mut prepended = 0u64;
1355        match codec {
1356            Codec::H264 => {
1357                if let Some(sps) = &self.h264_sps {
1358                    append_annex_b(data, sps);
1359                    prepended += 1;
1360                }
1361                if let Some(pps) = &self.h264_pps {
1362                    append_annex_b(data, pps);
1363                    prepended += 1;
1364                }
1365            }
1366            Codec::H265 => {
1367                if let Some(vps) = &self.h265_vps {
1368                    append_annex_b(data, vps);
1369                    prepended += 1;
1370                }
1371                if let Some(sps) = &self.h265_sps {
1372                    append_annex_b(data, sps);
1373                    prepended += 1;
1374                }
1375                if let Some(pps) = &self.h265_pps {
1376                    append_annex_b(data, pps);
1377                    prepended += 1;
1378                }
1379            }
1380        }
1381        if prepended > 0 {
1382            self.status.keyframes_with_prepended_config = self
1383                .status
1384                .keyframes_with_prepended_config
1385                .saturating_add(1);
1386            self.status.parameter_sets_prepended = self
1387                .status
1388                .parameter_sets_prepended
1389                .saturating_add(prepended);
1390        }
1391    }
1392
1393    fn has_decoder_config(&self, codec: Codec) -> bool {
1394        match codec {
1395            Codec::H264 => self.h264_sps.is_some() && self.h264_pps.is_some(),
1396            Codec::H265 => {
1397                self.h265_vps.is_some() && self.h265_sps.is_some() && self.h265_pps.is_some()
1398            }
1399        }
1400    }
1401
1402    fn record_error(&mut self, err: RtpError) {
1403        match err {
1404            RtpError::UnsupportedPayload => {
1405                self.status.unsupported_payloads =
1406                    self.status.unsupported_payloads.saturating_add(1);
1407            }
1408            RtpError::FragmentOverflow => {}
1409            _ => {
1410                self.status.malformed_packets = self.status.malformed_packets.saturating_add(1);
1411            }
1412        }
1413    }
1414}
1415
1416fn reset_access_unit_state(state: &mut AccessUnitState) {
1417    state.data.clear();
1418    state.timestamp = None;
1419    state.next_sequence = None;
1420    state.corrupted = false;
1421    state.damage = FrameDamage::None;
1422    state.is_keyframe = false;
1423    state.has_decoder_config = false;
1424    state.nal_type = 0;
1425}
1426
1427fn codec_from_payload_type(payload_type: u8) -> Option<Codec> {
1428    match payload_type {
1429        RTP_PAYLOAD_TYPE_H264 => Some(Codec::H264),
1430        RTP_PAYLOAD_TYPE_H265 => Some(Codec::H265),
1431        _ => None,
1432    }
1433}
1434
1435fn codec_parameter_set_signature(payload: &[u8]) -> Option<Codec> {
1436    let first = *payload.first()?;
1437    let h264_nal_type = first & 0x1f;
1438    if matches!(h264_nal_type, 7 | 8) {
1439        return Some(Codec::H264);
1440    }
1441    let h265_nal_type = (first >> 1) & 0x3f;
1442    let valid_h265_header =
1443        first & 0x01 == 0 && payload.get(1).is_some_and(|second| second & 0x07 != 0);
1444    (valid_h265_header && matches!(h265_nal_type, 32..=34)).then_some(Codec::H265)
1445}
1446
1447struct AnnexBNalus<'a> {
1448    bytes: &'a [u8],
1449    cursor: Option<usize>,
1450}
1451
1452impl<'a> AnnexBNalus<'a> {
1453    fn new(bytes: &'a [u8]) -> Self {
1454        Self {
1455            bytes,
1456            cursor: annex_b_start_code_len(bytes, 0),
1457        }
1458    }
1459}
1460
1461impl<'a> Iterator for AnnexBNalus<'a> {
1462    type Item = &'a [u8];
1463
1464    fn next(&mut self) -> Option<Self::Item> {
1465        let start = self.cursor?;
1466        let mut next_start = None;
1467        let mut index = start;
1468        while index + 3 <= self.bytes.len() {
1469            if let Some(length) = annex_b_start_code_len(self.bytes, index) {
1470                next_start = Some((index, length));
1471                break;
1472            }
1473            index += 1;
1474        }
1475        let end = next_start.map_or(self.bytes.len(), |(index, _)| index);
1476        self.cursor = next_start.map(|(index, length)| index + length);
1477        (end > start).then(|| &self.bytes[start..end])
1478    }
1479}
1480
1481fn annex_b_start_code_len(bytes: &[u8], offset: usize) -> Option<usize> {
1482    let tail = bytes.get(offset..)?;
1483    if tail.starts_with(&[0, 0, 0, 1]) {
1484        Some(4)
1485    } else if tail.starts_with(&[0, 0, 1]) {
1486        Some(3)
1487    } else {
1488        None
1489    }
1490}
1491
1492fn detect_codec(payload: &[u8]) -> Option<Codec> {
1493    if payload.is_empty() {
1494        return None;
1495    }
1496    if payload.len() >= 2 {
1497        let h265_nal_type = (payload[0] >> 1) & 0x3f;
1498        if h265_nal_type == 48 || h265_nal_type == 49 || (32..=40).contains(&h265_nal_type) {
1499            return Some(Codec::H265);
1500        }
1501    }
1502    let h264_nal_type = payload[0] & 0x1f;
1503    if h264_nal_type == 24 || h264_nal_type == 28 || (1..=12).contains(&h264_nal_type) {
1504        return Some(Codec::H264);
1505    }
1506    None
1507}
1508
1509fn is_h264_vcl_nal(nal_type: u8) -> bool {
1510    (1..=5).contains(&nal_type)
1511}
1512
1513fn is_h265_vcl_nal(nal_type: u8) -> bool {
1514    nal_type <= 31
1515}
1516
1517fn append_annex_b(out: &mut Vec<u8>, nalu: &[u8]) {
1518    out.extend_from_slice(&[0, 0, 0, 1]);
1519    out.extend_from_slice(nalu);
1520}
1521
1522#[cfg(test)]
1523mod tests {
1524    use super::*;
1525
1526    fn rtp(payload: &[u8], marker: bool, seq: u16, timestamp: u32) -> Vec<u8> {
1527        rtp_with_payload_type(payload, RTP_PAYLOAD_TYPE_H264, marker, seq, timestamp)
1528    }
1529
1530    fn rtp_with_payload_type(
1531        payload: &[u8],
1532        payload_type: u8,
1533        marker: bool,
1534        seq: u16,
1535        timestamp: u32,
1536    ) -> Vec<u8> {
1537        let mut packet = vec![
1538            0x80,
1539            (if marker { 0x80 } else { 0x00 }) | (payload_type & 0x7f),
1540        ];
1541        packet.extend_from_slice(&seq.to_be_bytes());
1542        packet.extend_from_slice(&timestamp.to_be_bytes());
1543        packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
1544        packet.extend_from_slice(payload);
1545        packet
1546    }
1547
1548    fn stap_a(units: &[&[u8]]) -> Vec<u8> {
1549        let mut payload = vec![24];
1550        for unit in units {
1551            payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
1552            payload.extend_from_slice(unit);
1553        }
1554        payload
1555    }
1556
1557    fn h265_ap(units: &[&[u8]]) -> Vec<u8> {
1558        let mut payload = vec![0x60, 0x01];
1559        for unit in units {
1560            payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
1561            payload.extend_from_slice(unit);
1562        }
1563        payload
1564    }
1565
1566    fn prime_h264(depay: &mut RtpDepacketizer) {
1567        assert!(depay
1568            .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
1569            .unwrap()
1570            .is_none());
1571        assert!(depay
1572            .push(&rtp(&[0x68, 0xee], true, 2, 10))
1573            .unwrap()
1574            .is_none());
1575    }
1576
1577    fn prime_h265(depay: &mut RtpDepacketizer) {
1578        for (seq, payload) in [
1579            (1, &[0x40, 0x01, 0xaa][..]),
1580            (2, &[0x42, 0x01, 0xbb][..]),
1581            (3, &[0x44, 0x01, 0xcc][..]),
1582        ] {
1583            assert!(depay
1584                .push(&rtp_with_payload_type(
1585                    payload,
1586                    RTP_PAYLOAD_TYPE_H265,
1587                    true,
1588                    seq,
1589                    10,
1590                ))
1591                .unwrap()
1592                .is_none());
1593        }
1594    }
1595
1596    #[test]
1597    fn parses_rtp_header() {
1598        let packet = rtp(&[0x65, 1, 2], true, 7, 1234);
1599        let header = RtpHeader::parse(&packet).unwrap();
1600        assert!(header.marker);
1601        assert_eq!(header.payload_type, 96);
1602        assert_eq!(header.sequence_number, 7);
1603        assert_eq!(header.timestamp, 1234);
1604        assert_eq!(header.payload(&packet), &[0x65, 1, 2]);
1605    }
1606
1607    #[test]
1608    fn depacketizes_h264_single_nalu_as_annex_b() {
1609        let mut depay = RtpDepacketizer::new();
1610        prime_h264(&mut depay);
1611        let frame = depay
1612            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
1613            .unwrap()
1614            .unwrap();
1615        assert_eq!(frame.codec, Codec::H264);
1616        assert!(frame.is_keyframe);
1617        assert_eq!(
1618            frame.data,
1619            [
1620                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
1621                &[0, 0, 0, 1, 0x68, 0xee][..],
1622                &[0, 0, 0, 1, 0x65, 0xaa][..],
1623            ]
1624            .concat()
1625        );
1626    }
1627
1628    #[test]
1629    fn combines_same_timestamp_h264_slices_until_marker() {
1630        let mut depay = RtpDepacketizer::new();
1631        prime_h264(&mut depay);
1632        assert!(depay
1633            .push(&rtp(&[0x41, 0x80, 0xaa], false, 3, 42))
1634            .unwrap()
1635            .is_none());
1636        let frame = depay
1637            .push(&rtp(&[0x41, 0x40, 0xbb], true, 4, 42))
1638            .unwrap()
1639            .unwrap();
1640
1641        assert_eq!(
1642            frame.data,
1643            [
1644                &[0, 0, 0, 1, 0x41, 0x80, 0xaa][..],
1645                &[0, 0, 0, 1, 0x41, 0x40, 0xbb][..],
1646            ]
1647            .concat()
1648        );
1649        assert_eq!(frame.timestamp, 42);
1650        assert!(!frame.is_keyframe);
1651    }
1652
1653    #[test]
1654    fn drops_access_unit_after_sequence_gap() {
1655        let mut depay = RtpDepacketizer::new();
1656        prime_h264(&mut depay);
1657        assert!(depay
1658            .push(&rtp(&[0x41, 0x80, 0xaa], false, 3, 42))
1659            .unwrap()
1660            .is_none());
1661        assert!(depay
1662            .push(&rtp(&[0x41, 0x40, 0xbb], true, 5, 42))
1663            .unwrap()
1664            .is_none());
1665
1666        assert_eq!(depay.status().fragment_sequence_gaps, 1);
1667        assert!(depay
1668            .push(&rtp(&[0x41, 0xcc], true, 6, 43))
1669            .unwrap()
1670            .is_some());
1671    }
1672
1673    #[test]
1674    fn forwards_damaged_access_unit_after_sequence_gap_in_fpv_mode() {
1675        let mut depay = RtpDepacketizer::new();
1676        depay.set_damaged_frame_policy(DamagedFramePolicy::Forward);
1677        prime_h264(&mut depay);
1678        assert!(depay
1679            .push(&rtp(&[0x41, 0x80, 0xaa], false, 3, 42))
1680            .unwrap()
1681            .is_none());
1682        let frame = depay
1683            .push(&rtp(&[0x41, 0x40, 0xbb], true, 5, 42))
1684            .unwrap()
1685            .unwrap();
1686
1687        assert!(frame.damaged);
1688        assert_eq!(frame.damage, FrameDamage::MissingSlice);
1689        assert_eq!(frame.timestamp, 42);
1690        assert_eq!(
1691            frame.data,
1692            [
1693                &[0, 0, 0, 1, 0x41, 0x80, 0xaa][..],
1694                &[0, 0, 0, 1, 0x41, 0x40, 0xbb][..],
1695            ]
1696            .concat()
1697        );
1698        assert_eq!(depay.status().damaged_frames_forwarded, 1);
1699        assert_eq!(depay.status().damaged_frames_dropped, 0);
1700    }
1701
1702    #[test]
1703    fn drops_h264_video_until_sps_and_pps_are_seen() {
1704        let mut depay = RtpDepacketizer::new();
1705        assert!(depay
1706            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
1707            .unwrap()
1708            .is_none());
1709        let status = depay.status();
1710        assert_eq!(status.config_wait_drops, 1);
1711        assert!(!status.codec_config.is_complete_for(Codec::H264));
1712        assert_eq!(status.last_nal_type, Some(5));
1713    }
1714
1715    #[test]
1716    fn h264_payload_type_prevents_h265_false_positive() {
1717        let mut depay = RtpDepacketizer::new();
1718        prime_h264(&mut depay);
1719        let frame = depay
1720            .push(&rtp(&[0x41, 0xaa], true, 1, 42))
1721            .unwrap()
1722            .unwrap();
1723        assert_eq!(frame.codec, Codec::H264);
1724        assert!(!frame.is_keyframe);
1725        assert_eq!(frame.data, &[0, 0, 0, 1, 0x41, 0xaa]);
1726    }
1727
1728    #[test]
1729    fn h264_non_vcl_nal_is_not_emitted_as_video_frame() {
1730        let mut depay = RtpDepacketizer::new();
1731        prime_h264(&mut depay);
1732        assert!(depay
1733            .push(&rtp(&[0x06, 0x05, 0xff], true, 3, 42))
1734            .unwrap()
1735            .is_none());
1736    }
1737
1738    #[test]
1739    fn opus_payload_type_is_not_sniffed_as_video() {
1740        let mut depay = RtpDepacketizer::new();
1741        prime_h264(&mut depay);
1742        let err = depay
1743            .push(&rtp_with_payload_type(
1744                &[0x65, 0xaa],
1745                RTP_PAYLOAD_TYPE_OPUS,
1746                true,
1747                1,
1748                42,
1749            ))
1750            .unwrap_err();
1751        assert_eq!(err, RtpError::UnsupportedPayload);
1752    }
1753
1754    #[test]
1755    fn depacketizes_h265_single_nalu_by_payload_type() {
1756        let mut depay = RtpDepacketizer::new();
1757        prime_h265(&mut depay);
1758        let frame = depay
1759            .push(&rtp_with_payload_type(
1760                &[0x02, 0x01, 0xaa],
1761                RTP_PAYLOAD_TYPE_H265,
1762                true,
1763                1,
1764                42,
1765            ))
1766            .unwrap()
1767            .unwrap();
1768        assert_eq!(frame.codec, Codec::H265);
1769        assert!(!frame.is_keyframe);
1770        assert_eq!(frame.data, &[0, 0, 0, 1, 0x02, 0x01, 0xaa]);
1771    }
1772
1773    #[test]
1774    fn h265_non_vcl_nal_is_not_emitted_as_video_frame() {
1775        let mut depay = RtpDepacketizer::new();
1776        prime_h265(&mut depay);
1777        assert!(depay
1778            .push(&rtp_with_payload_type(
1779                &[0x4e, 0x01, 0xff],
1780                RTP_PAYLOAD_TYPE_H265,
1781                true,
1782                4,
1783                42,
1784            ))
1785            .unwrap()
1786            .is_none());
1787    }
1788
1789    #[test]
1790    fn h264_stap_a_caches_parameter_sets_for_later_keyframe() {
1791        let mut depay = RtpDepacketizer::new();
1792        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1793        let pps = &[0x68, 0xee][..];
1794        let aggregate = depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
1795        assert!(aggregate.is_none());
1796
1797        let frame = depay
1798            .push(&rtp(&[0x65, 0xaa], true, 2, 20))
1799            .unwrap()
1800            .unwrap();
1801        assert!(frame.is_keyframe);
1802        assert_eq!(
1803            frame.data,
1804            [
1805                &[0, 0, 0, 1][..],
1806                sps,
1807                &[0, 0, 0, 1][..],
1808                pps,
1809                &[0, 0, 0, 1, 0x65, 0xaa][..],
1810            ]
1811            .concat()
1812        );
1813    }
1814
1815    #[test]
1816    fn h264_stap_a_prepends_cached_parameter_sets_for_idr_without_inband_config() {
1817        let mut depay = RtpDepacketizer::new();
1818        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1819        let pps = &[0x68, 0xee][..];
1820        depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
1821
1822        let frame = depay
1823            .push(&rtp(&stap_a(&[&[0x65, 0xaa, 0xbb]]), true, 2, 20))
1824            .unwrap()
1825            .unwrap();
1826
1827        assert!(frame.is_keyframe);
1828        assert_eq!(
1829            frame.data,
1830            [
1831                &[0, 0, 0, 1][..],
1832                sps,
1833                &[0, 0, 0, 1][..],
1834                pps,
1835                &[0, 0, 0, 1, 0x65, 0xaa, 0xbb][..],
1836            ]
1837            .concat()
1838        );
1839        let status = depay.status();
1840        assert_eq!(status.keyframes_with_prepended_config, 1);
1841        assert_eq!(status.parameter_sets_prepended, 2);
1842    }
1843
1844    #[test]
1845    fn h264_stap_a_does_not_duplicate_inband_parameter_sets() {
1846        let mut depay = RtpDepacketizer::new();
1847        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1848        let pps = &[0x68, 0xee][..];
1849        let frame = depay
1850            .push(&rtp(&stap_a(&[sps, pps, &[0x65, 0xaa]]), true, 1, 20))
1851            .unwrap()
1852            .unwrap();
1853
1854        assert_eq!(
1855            frame.data,
1856            [
1857                &[0, 0, 0, 1][..],
1858                sps,
1859                &[0, 0, 0, 1][..],
1860                pps,
1861                &[0, 0, 0, 1, 0x65, 0xaa][..],
1862            ]
1863            .concat()
1864        );
1865        let status = depay.status();
1866        assert_eq!(status.keyframes_with_prepended_config, 0);
1867        assert_eq!(status.parameter_sets_prepended, 0);
1868    }
1869
1870    #[test]
1871    fn h264_stap_a_waits_for_the_access_unit_marker() {
1872        let mut depay = RtpDepacketizer::new();
1873        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
1874        let pps = &[0x68, 0xee][..];
1875        assert!(depay
1876            .push(&rtp(&stap_a(&[sps, pps, &[0x61, 0xaa]]), false, 1, 20,))
1877            .unwrap()
1878            .is_none());
1879
1880        let frame = depay
1881            .push(&rtp(&[0x61, 0xbb], true, 2, 20))
1882            .unwrap()
1883            .unwrap();
1884        assert_eq!(
1885            frame.data,
1886            [
1887                &[0, 0, 0, 1][..],
1888                sps,
1889                &[0, 0, 0, 1][..],
1890                pps,
1891                &[0, 0, 0, 1, 0x61, 0xaa][..],
1892                &[0, 0, 0, 1, 0x61, 0xbb][..],
1893            ]
1894            .concat()
1895        );
1896    }
1897
1898    #[test]
1899    fn malformed_stap_a_length_is_rejected() {
1900        let mut depay = RtpDepacketizer::new();
1901        let malformed = [24, 0, 8, 0x67, 0x64];
1902        assert_eq!(
1903            depay.push(&rtp(&malformed, true, 1, 20)),
1904            Err(RtpError::UnsupportedPayload)
1905        );
1906        assert_eq!(depay.status().unsupported_payloads, 1);
1907    }
1908
1909    #[test]
1910    fn h265_ap_prepends_cached_parameter_sets_for_keyframe_without_inband_config() {
1911        let mut depay = RtpDepacketizer::new();
1912        prime_h265(&mut depay);
1913        let frame = depay
1914            .push(&rtp_with_payload_type(
1915                &h265_ap(&[&[0x26, 0x01, 0xaa]]),
1916                RTP_PAYLOAD_TYPE_H265,
1917                true,
1918                4,
1919                20,
1920            ))
1921            .unwrap()
1922            .unwrap();
1923
1924        assert!(frame.is_keyframe);
1925        assert_eq!(
1926            frame.data,
1927            [
1928                &[0, 0, 0, 1, 0x40, 0x01, 0xaa][..],
1929                &[0, 0, 0, 1, 0x42, 0x01, 0xbb][..],
1930                &[0, 0, 0, 1, 0x44, 0x01, 0xcc][..],
1931                &[0, 0, 0, 1, 0x26, 0x01, 0xaa][..],
1932            ]
1933            .concat()
1934        );
1935        let status = depay.status();
1936        assert_eq!(status.keyframes_with_prepended_config, 1);
1937        assert_eq!(status.parameter_sets_prepended, 3);
1938    }
1939
1940    #[test]
1941    fn waybeam_separate_parameter_sets_bootstrap_an_idr() {
1942        // Mirrors waybeam_venc's hevc_rtp test contract: PT 97, no type-48
1943        // aggregation, separate VPS/SPS/PPS packets, and marker only on IDR.
1944        let packets: [&[u8]; 4] = [
1945            &[
1946                0x80, 0x61, 0x01, 0x00, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x40, 0x01, 0xaa,
1947            ],
1948            &[
1949                0x80, 0x61, 0x01, 0x01, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x42, 0x01, 0xbb,
1950            ],
1951            &[
1952                0x80, 0x61, 0x01, 0x02, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x44, 0x01, 0xcc,
1953            ],
1954            &[
1955                0x80, 0xe1, 0x01, 0x03, 0, 0, 0x10, 0, 0, 0, 0xde, 0xad, 0x26, 0x01, 0xdd,
1956            ],
1957        ];
1958        let mut depay = RtpDepacketizer::new();
1959        let mut frame = None;
1960        for packet in packets {
1961            if let Some(output) = depay.push(packet).unwrap() {
1962                frame = Some(output);
1963            }
1964        }
1965
1966        let frame = frame.expect("Waybeam IDR should produce an access unit");
1967        assert_eq!(frame.codec, Codec::H265);
1968        assert!(frame.is_keyframe);
1969        assert_eq!(
1970            frame.data,
1971            [
1972                &[0, 0, 0, 1, 0x40, 0x01, 0xaa][..],
1973                &[0, 0, 0, 1, 0x42, 0x01, 0xbb][..],
1974                &[0, 0, 0, 1, 0x44, 0x01, 0xcc][..],
1975                &[0, 0, 0, 1, 0x26, 0x01, 0xdd][..],
1976            ]
1977            .concat()
1978        );
1979    }
1980
1981    #[test]
1982    fn divinus_h265_payload_type_96_is_detected_from_parameter_sets() {
1983        // Divinus uses PT 96 for both H.264 and H.265. Its VPS/SPS/PPS NAL
1984        // units are nevertheless unambiguous and lock automatic detection.
1985        let mut depay = RtpDepacketizer::new();
1986        for (sequence, timestamp, payload) in [
1987            (1, 1_000, &[0x40, 0x01, 0xaa][..]),
1988            (2, 4_000, &[0x42, 0x01, 0xbb][..]),
1989            (3, 7_000, &[0x44, 0x01, 0xcc][..]),
1990        ] {
1991            assert!(depay
1992                .push(&rtp_with_payload_type(
1993                    payload,
1994                    RTP_PAYLOAD_TYPE_H264,
1995                    false,
1996                    sequence,
1997                    timestamp,
1998                ))
1999                .unwrap()
2000                .is_none());
2001        }
2002
2003        let frame = depay
2004            .push(&rtp_with_payload_type(
2005                &[0x28, 0x01, 0xdd],
2006                RTP_PAYLOAD_TYPE_H264,
2007                true,
2008                4,
2009                10_000,
2010            ))
2011            .unwrap()
2012            .unwrap();
2013        assert_eq!(frame.codec, Codec::H265);
2014        assert!(frame.is_keyframe);
2015        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x28, 0x01, 0xdd]));
2016    }
2017
2018    #[test]
2019    fn divinus_annex_b_bundle_is_split_without_copying() {
2020        let payload = [
2021            &[0, 0, 0, 1, 0x40, 0x01, 0xaa][..],
2022            &[0, 0, 0, 1, 0x42, 0x01, 0xbb][..],
2023            &[0, 0, 0, 1, 0x44, 0x01, 0xcc][..],
2024            &[0, 0, 0, 1, 0x26, 0x01, 0xdd][..],
2025        ]
2026        .concat();
2027        let mut depay = RtpDepacketizer::new();
2028        let frame = depay
2029            .push(&rtp_with_payload_type(
2030                &payload,
2031                RTP_PAYLOAD_TYPE_H264,
2032                true,
2033                1,
2034                3_000,
2035            ))
2036            .unwrap()
2037            .unwrap();
2038
2039        assert_eq!(frame.codec, Codec::H265);
2040        assert!(frame.is_keyframe);
2041        assert_eq!(frame.data, payload);
2042    }
2043
2044    #[test]
2045    fn divinus_unmarked_picture_finishes_when_timestamp_advances() {
2046        let mut depay = RtpDepacketizer::new();
2047        prime_h264(&mut depay);
2048        assert!(depay
2049            .push(&rtp(&[0x61, 0xaa], false, 3, 3_000))
2050            .unwrap()
2051            .is_none());
2052
2053        let frame = depay
2054            .push(&rtp(&[0x61, 0xbb], false, 4, 6_000))
2055            .unwrap()
2056            .unwrap();
2057        assert_eq!(frame.codec, Codec::H264);
2058        assert_eq!(frame.timestamp, 3_000);
2059        assert_eq!(frame.data, [0, 0, 0, 1, 0x61, 0xaa]);
2060    }
2061
2062    #[test]
2063    fn divinus_legacy_two_byte_h265_fu_is_reassembled() {
2064        let mut depay = RtpDepacketizer::new();
2065        for (sequence, payload) in [
2066            (1, &[0x40, 0x01, 0xaa][..]),
2067            (2, &[0x42, 0x01, 0xbb][..]),
2068            (3, &[0x44, 0x01, 0xcc][..]),
2069        ] {
2070            depay
2071                .push(&rtp_with_payload_type(
2072                    payload,
2073                    RTP_PAYLOAD_TYPE_H264,
2074                    false,
2075                    sequence,
2076                    10,
2077                ))
2078                .unwrap();
2079        }
2080
2081        // Literal wire shape produced by Divinus stream.c for an HEVC type-19
2082        // NAL beginning 26 01 aa bb cc dd. It omits the RFC 7798 layer/TID byte.
2083        assert!(depay
2084            .push(&rtp(&[0x62, 0xa6, 0xaa, 0xbb], false, 4, 3_000))
2085            .unwrap()
2086            .is_none());
2087        assert!(depay
2088            .push(&rtp(&[0x62, 0x66, 0xcc, 0xdd], false, 5, 3_000))
2089            .unwrap()
2090            .is_none());
2091
2092        let frame = depay
2093            .push(&rtp(&[0x02, 0x01, 0xee], false, 6, 6_000))
2094            .unwrap()
2095            .unwrap();
2096        assert_eq!(frame.codec, Codec::H265);
2097        assert!(frame.is_keyframe);
2098        assert!(frame
2099            .data
2100            .ends_with(&[0, 0, 0, 1, 0x26, 0x01, 0xaa, 0xbb, 0xcc, 0xdd]));
2101    }
2102
2103    #[test]
2104    fn waybeam_hevc_fu_vector_round_trips() {
2105        let mut depay = RtpDepacketizer::new();
2106        for (sequence, payload) in [
2107            (0x1ffd, &[0x40, 0x01, 0xaa][..]),
2108            (0x1ffe, &[0x42, 0x01, 0xbb][..]),
2109            (0x1fff, &[0x44, 0x01, 0xcc][..]),
2110        ] {
2111            depay
2112                .push(&rtp_with_payload_type(
2113                    payload,
2114                    RTP_PAYLOAD_TYPE_H265,
2115                    false,
2116                    sequence,
2117                    0x0101_0100,
2118                ))
2119                .unwrap();
2120        }
2121
2122        // Literal output from Waybeam's max_payload=5 FU test for the NAL
2123        // 26 01 aa bb cc dd ee. The final packet alone carries the marker.
2124        let fragments: [&[u8]; 3] = [
2125            &[
2126                0x80, 0x61, 0x20, 0x00, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x93, 0xaa, 0xbb,
2127            ],
2128            &[
2129                0x80, 0x61, 0x20, 0x01, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x13, 0xcc, 0xdd,
2130            ],
2131            &[
2132                0x80, 0xe1, 0x20, 0x02, 1, 1, 1, 1, 2, 2, 2, 2, 0x62, 0x01, 0x53, 0xee,
2133            ],
2134        ];
2135        let mut frame = None;
2136        for packet in fragments {
2137            if let Some(output) = depay.push(packet).unwrap() {
2138                frame = Some(output);
2139            }
2140        }
2141
2142        let frame = frame.expect("Waybeam FU chain should produce an access unit");
2143        assert_eq!(frame.codec, Codec::H265);
2144        assert!(frame.is_keyframe);
2145        assert!(frame
2146            .data
2147            .ends_with(&[0, 0, 0, 1, 0x26, 0x01, 0xaa, 0xbb, 0xcc, 0xdd, 0xee]));
2148    }
2149
2150    #[test]
2151    fn waybeam_refpred_trail_n_fu_is_accepted() {
2152        let mut depay = RtpDepacketizer::new();
2153        prime_h265(&mut depay);
2154        let fragments: [&[u8]; 2] = [
2155            &[
2156                0x80, 0x61, 0x07, 0x00, 0, 0, 0x70, 0, 0, 0, 0x43, 0x21, 0x62, 0x01, 0x80, 0x55,
2157                0x66,
2158            ],
2159            &[
2160                0x80, 0xe1, 0x07, 0x01, 0, 0, 0x70, 0, 0, 0, 0x43, 0x21, 0x62, 0x01, 0x40, 0x77,
2161                0x88,
2162            ],
2163        ];
2164        let mut frame = None;
2165        for packet in fragments {
2166            if let Some(output) = depay.push(packet).unwrap() {
2167                frame = Some(output);
2168            }
2169        }
2170
2171        let frame = frame.expect("Waybeam TRAIL_N FU chain should be emitted");
2172        assert_eq!(frame.codec, Codec::H265);
2173        assert!(!frame.is_keyframe);
2174        assert_eq!(frame.nal_type, 0);
2175        assert_eq!(
2176            frame.data,
2177            &[0, 0, 0, 1, 0x00, 0x01, 0x55, 0x66, 0x77, 0x88]
2178        );
2179    }
2180
2181    #[test]
2182    fn depacketizes_h264_fu_a() {
2183        let mut depay = RtpDepacketizer::new();
2184        prime_h264(&mut depay);
2185        assert!(depay
2186            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 1, 99))
2187            .unwrap()
2188            .is_none());
2189        let frame = depay
2190            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 2, 99))
2191            .unwrap()
2192            .unwrap();
2193        assert_eq!(
2194            frame.data,
2195            [
2196                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
2197                &[0, 0, 0, 1, 0x68, 0xee][..],
2198                &[0, 0, 0, 1, 0x65, 1, 2, 3, 4][..],
2199            ]
2200            .concat()
2201        );
2202    }
2203
2204    #[test]
2205    fn drops_h264_fu_a_after_sequence_gap() {
2206        let mut depay = RtpDepacketizer::new();
2207        prime_h264(&mut depay);
2208        assert!(depay
2209            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 10, 99))
2210            .unwrap()
2211            .is_none());
2212        assert!(depay
2213            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 12, 99))
2214            .unwrap()
2215            .is_none());
2216
2217        assert!(depay
2218            .push(&rtp(&[0x7c, 0x85, 5, 6], false, 13, 100))
2219            .unwrap()
2220            .is_none());
2221        let frame = depay
2222            .push(&rtp(&[0x7c, 0x45, 7, 8], true, 14, 100))
2223            .unwrap()
2224            .unwrap();
2225        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 5, 6, 7, 8]));
2226    }
2227
2228    #[test]
2229    fn forwards_partial_h264_fu_after_sequence_gap_in_fpv_mode() {
2230        let mut depay = RtpDepacketizer::new();
2231        depay.set_damaged_frame_policy(DamagedFramePolicy::Forward);
2232        prime_h264(&mut depay);
2233        assert!(depay
2234            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 10, 99))
2235            .unwrap()
2236            .is_none());
2237        let frame = depay
2238            .push(&rtp(&[0x7c, 0x45, 5, 6], true, 12, 99))
2239            .unwrap()
2240            .unwrap();
2241
2242        assert!(frame.damaged);
2243        assert_eq!(frame.damage, FrameDamage::TruncatedFragment);
2244        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 1, 2]));
2245        assert_eq!(depay.status().fragment_sequence_gaps, 1);
2246        assert_eq!(depay.status().damaged_frames_forwarded, 1);
2247    }
2248
2249    #[test]
2250    fn forwards_partial_h265_fu_after_sequence_gap_in_fpv_mode() {
2251        let mut depay = RtpDepacketizer::new();
2252        depay.set_damaged_frame_policy(DamagedFramePolicy::Forward);
2253        prime_h265(&mut depay);
2254        assert!(depay
2255            .push(&rtp_with_payload_type(
2256                &[0x62, 0x01, 0x93, 0xaa, 0xbb],
2257                RTP_PAYLOAD_TYPE_H265,
2258                false,
2259                10,
2260                99,
2261            ))
2262            .unwrap()
2263            .is_none());
2264        let frame = depay
2265            .push(&rtp_with_payload_type(
2266                &[0x62, 0x01, 0x53, 0xee],
2267                RTP_PAYLOAD_TYPE_H265,
2268                true,
2269                12,
2270                99,
2271            ))
2272            .unwrap()
2273            .unwrap();
2274
2275        assert!(frame.damaged);
2276        assert_eq!(frame.damage, FrameDamage::TruncatedFragment);
2277        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x26, 0x01, 0xaa, 0xbb]));
2278        assert_eq!(depay.status().fragment_sequence_gaps, 1);
2279        assert_eq!(depay.status().damaged_frames_forwarded, 1);
2280    }
2281
2282    #[test]
2283    fn timestamp_transition_flushes_incomplete_fu_in_fpv_mode() {
2284        let mut depay = RtpDepacketizer::new();
2285        depay.set_damaged_frame_policy(DamagedFramePolicy::Forward);
2286        prime_h264(&mut depay);
2287        assert!(depay
2288            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 3, 99))
2289            .unwrap()
2290            .is_none());
2291
2292        let frame = depay
2293            .push(&rtp(&[0x41, 0xaa], true, 4, 100))
2294            .unwrap()
2295            .unwrap();
2296        assert!(frame.damaged);
2297        assert_eq!(frame.damage, FrameDamage::TruncatedFragment);
2298        assert_eq!(frame.timestamp, 99);
2299        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 1, 2]));
2300        assert_eq!(depay.status().damaged_frames_forwarded, 1);
2301    }
2302
2303    #[test]
2304    fn drops_fragment_end_without_start() {
2305        let mut depay = RtpDepacketizer::new();
2306        prime_h264(&mut depay);
2307        assert!(depay
2308            .push(&rtp(&[0x7c, 0x45, 1, 2], true, 10, 99))
2309            .unwrap()
2310            .is_none());
2311    }
2312
2313    #[test]
2314    fn status_tracks_h264_decoder_config() {
2315        let mut depay = RtpDepacketizer::new();
2316        depay
2317            .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
2318            .unwrap();
2319        let status = depay.status();
2320        assert!(status.codec_config.h264_sps);
2321        assert!(!status.codec_config.h264_pps);
2322        assert!(!status.codec_config.is_complete_for(Codec::H264));
2323
2324        depay.push(&rtp(&[0x68, 0xee], true, 2, 10)).unwrap();
2325        let status = depay.status();
2326        assert!(status.codec_config.is_complete_for(Codec::H264));
2327    }
2328
2329    #[test]
2330    fn reorder_buffer_restores_short_out_of_order_burst() {
2331        let mut reorder = RtpReorderBuffer::default();
2332        let first = rtp(&[0x61, 1], true, 10, 90);
2333        let second = rtp(&[0x61, 2], true, 11, 90);
2334        let third = rtp(&[0x61, 3], true, 12, 90);
2335
2336        assert_eq!(reorder.push(&first).unwrap(), vec![first.clone()]);
2337        assert!(reorder.push(&third).unwrap().is_empty());
2338        assert_eq!(reorder.status().buffered_packets, 1);
2339        assert_eq!(reorder.status().reordered_packets, 1);
2340
2341        let ready = reorder.push(&second).unwrap();
2342        assert_eq!(ready, vec![second, third]);
2343        assert_eq!(reorder.status().buffered_packets, 0);
2344    }
2345}