Skip to main content

openipc_core/
rtp.rs

1/// Error returned while parsing or depacketizing RTP video.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum RtpError {
4    /// Packet is shorter than the RTP fixed header or declared extension.
5    TooShort,
6    /// RTP version was not 2.
7    InvalidVersion(u8),
8    /// RTP extension header length is malformed.
9    InvalidExtension,
10    /// RTP padding length is malformed.
11    InvalidPadding,
12    /// Packet has no payload after header/extension/padding.
13    EmptyPayload,
14    /// Payload could not be interpreted as H.264 or H.265.
15    UnsupportedPayload,
16    /// Fragmented access unit exceeded the configured size guard.
17    FragmentOverflow,
18}
19
20/// Encoded video codec carried by a depacketized frame.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Codec {
23    /// H.264/AVC video.
24    H264,
25    /// H.265/HEVC video.
26    H265,
27}
28
29/// Dynamic RTP payload type used by OpenIPC for H.264.
30pub const RTP_PAYLOAD_TYPE_H264: u8 = 96;
31/// Dynamic RTP payload type used by OpenIPC for H.265.
32pub const RTP_PAYLOAD_TYPE_H265: u8 = 97;
33/// Dynamic RTP payload type used by OpenIPC/Majestic for Opus audio.
34pub const RTP_PAYLOAD_TYPE_OPUS: u8 = 98;
35
36/// Parsed RTP header metadata.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct RtpHeader {
39    /// RTP marker bit, usually set at an access-unit boundary.
40    pub marker: bool,
41    /// RTP payload type.
42    pub payload_type: u8,
43    /// RTP sequence number.
44    pub sequence_number: u16,
45    /// RTP timestamp.
46    pub timestamp: u32,
47    /// RTP synchronization source.
48    pub ssrc: u32,
49    /// Number of CSRC entries.
50    pub csrc_count: u8,
51    /// True if the packet has an RTP header extension.
52    pub has_extension: bool,
53    /// Header length including CSRC and extension bytes.
54    pub header_len: usize,
55    /// Payload length after header and padding removal.
56    pub payload_len: usize,
57}
58
59impl RtpHeader {
60    /// Parse an RTP header and validate extension/padding bounds.
61    pub fn parse(packet: &[u8]) -> Result<Self, RtpError> {
62        if packet.len() < 12 {
63            return Err(RtpError::TooShort);
64        }
65        let version = packet[0] >> 6;
66        if version != 2 {
67            return Err(RtpError::InvalidVersion(version));
68        }
69
70        let padding = packet[0] & 0x20 != 0;
71        let extension = packet[0] & 0x10 != 0;
72        let csrc_count = packet[0] & 0x0f;
73        let mut header_len = 12 + csrc_count as usize * 4;
74        if packet.len() < header_len {
75            return Err(RtpError::TooShort);
76        }
77
78        if extension {
79            if packet.len() < header_len + 4 {
80                return Err(RtpError::InvalidExtension);
81            }
82            let ext_words =
83                u16::from_be_bytes([packet[header_len + 2], packet[header_len + 3]]) as usize;
84            header_len += 4 + ext_words * 4;
85            if packet.len() < header_len {
86                return Err(RtpError::InvalidExtension);
87            }
88        }
89
90        let padding_len = if padding {
91            let len = *packet.last().ok_or(RtpError::InvalidPadding)? as usize;
92            if len == 0 || len > packet.len() - header_len {
93                return Err(RtpError::InvalidPadding);
94            }
95            len
96        } else {
97            0
98        };
99
100        let payload_len = packet.len() - header_len - padding_len;
101        if payload_len == 0 {
102            return Err(RtpError::EmptyPayload);
103        }
104
105        Ok(Self {
106            marker: packet[1] & 0x80 != 0,
107            payload_type: packet[1] & 0x7f,
108            sequence_number: u16::from_be_bytes([packet[2], packet[3]]),
109            timestamp: u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]),
110            ssrc: u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
111            csrc_count,
112            has_extension: extension,
113            header_len,
114            payload_len,
115        })
116    }
117
118    /// Borrow this packet's payload using the parsed header offsets.
119    pub fn payload<'a>(&self, packet: &'a [u8]) -> &'a [u8] {
120        &packet[self.header_len..self.header_len + self.payload_len]
121    }
122}
123
124/// Encoded Annex-B H.264/H.265 access unit emitted by [`RtpDepacketizer`].
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct DepacketizedFrame {
127    /// Annex-B byte stream data including start codes.
128    pub data: Vec<u8>,
129    /// RTP timestamp associated with the access unit.
130    pub timestamp: u32,
131    /// True when the frame contains an IDR/keyframe entry point.
132    pub is_keyframe: bool,
133    /// Video codec for this frame.
134    pub codec: Codec,
135}
136
137#[derive(Debug, Default, Clone)]
138struct FragmentState {
139    data: Vec<u8>,
140    timestamp: u32,
141    next_sequence: Option<u16>,
142    corrupted: bool,
143}
144
145/// Stateful RTP depacketizer for OpenIPC H.264/H.265 video.
146///
147/// The depacketizer buffers fragmented NAL units, drops incomplete fragments
148/// across sequence gaps, and emits complete Annex-B access units.
149#[derive(Debug, Clone)]
150pub struct RtpDepacketizer {
151    h264: FragmentState,
152    h265: FragmentState,
153    h264_sps: Option<Vec<u8>>,
154    h264_pps: Option<Vec<u8>>,
155    h265_vps: Option<Vec<u8>>,
156    h265_sps: Option<Vec<u8>>,
157    h265_pps: Option<Vec<u8>>,
158    max_fragment: usize,
159}
160
161impl Default for RtpDepacketizer {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl RtpDepacketizer {
168    /// Create a depacketizer with the default fragment-size guard.
169    pub fn new() -> Self {
170        Self {
171            h264: FragmentState::default(),
172            h265: FragmentState::default(),
173            h264_sps: None,
174            h264_pps: None,
175            h265_vps: None,
176            h265_sps: None,
177            h265_pps: None,
178            max_fragment: 1024 * 1024,
179        }
180    }
181
182    /// Push one RTP packet and return a complete frame when one is ready.
183    pub fn push(&mut self, packet: &[u8]) -> Result<Option<DepacketizedFrame>, RtpError> {
184        let header = RtpHeader::parse(packet)?;
185        let payload = header.payload(packet);
186        if header.payload_type == RTP_PAYLOAD_TYPE_OPUS {
187            return Err(RtpError::UnsupportedPayload);
188        }
189        let codec = codec_from_payload_type(header.payload_type)
190            .or_else(|| detect_codec(payload))
191            .ok_or(RtpError::UnsupportedPayload)?;
192        match codec {
193            Codec::H264 => self.push_h264(payload, header),
194            Codec::H265 => self.push_h265(payload, header),
195        }
196    }
197
198    fn push_h264(
199        &mut self,
200        payload: &[u8],
201        header: RtpHeader,
202    ) -> Result<Option<DepacketizedFrame>, RtpError> {
203        let nal_type = payload[0] & 0x1f;
204        match nal_type {
205            7 => {
206                self.h264_sps = Some(payload.to_vec());
207                Ok(None)
208            }
209            8 => {
210                self.h264_pps = Some(payload.to_vec());
211                Ok(None)
212            }
213            24 => self.h264_stap_a(payload, header.timestamp),
214            28 => self.h264_fu_a(payload, header),
215            _ if self.has_decoder_config(Codec::H264) => Ok(Some(self.frame_with_prefix(
216                payload,
217                header.timestamp,
218                nal_type == 5,
219                Codec::H264,
220            ))),
221            _ => Ok(None),
222        }
223    }
224
225    fn push_h265(
226        &mut self,
227        payload: &[u8],
228        header: RtpHeader,
229    ) -> Result<Option<DepacketizedFrame>, RtpError> {
230        if payload.len() < 2 {
231            return Err(RtpError::UnsupportedPayload);
232        }
233        let nal_type = (payload[0] >> 1) & 0x3f;
234        match nal_type {
235            32 => {
236                self.h265_vps = Some(payload.to_vec());
237                Ok(None)
238            }
239            33 => {
240                self.h265_sps = Some(payload.to_vec());
241                Ok(None)
242            }
243            34 => {
244                self.h265_pps = Some(payload.to_vec());
245                Ok(None)
246            }
247            48 => self.h265_ap(payload, header.timestamp),
248            49 => self.h265_fu(payload, header),
249            _ if self.has_decoder_config(Codec::H265) => Ok(Some(self.frame_with_prefix(
250                payload,
251                header.timestamp,
252                (16..=23).contains(&nal_type),
253                Codec::H265,
254            ))),
255            _ => Ok(None),
256        }
257    }
258
259    fn h264_fu_a(
260        &mut self,
261        payload: &[u8],
262        header: RtpHeader,
263    ) -> Result<Option<DepacketizedFrame>, RtpError> {
264        if payload.len() < 2 {
265            return Err(RtpError::UnsupportedPayload);
266        }
267        let fu_indicator = payload[0];
268        let fu_header = payload[1];
269        let start = fu_header & 0x80 != 0;
270        let end = fu_header & 0x40 != 0;
271        let nal_type = fu_header & 0x1f;
272        if start {
273            self.h264.data.clear();
274            self.h264.timestamp = header.timestamp;
275            self.h264.next_sequence = Some(header.sequence_number.wrapping_add(1));
276            self.h264.corrupted = false;
277            self.h264.data.push((fu_indicator & 0xe0) | nal_type);
278        } else if !self.accept_fragment_sequence(Codec::H264, header.sequence_number) {
279            return Ok(None);
280        }
281        if !self.h264.corrupted {
282            self.append_fragment(Codec::H264, &payload[2..])?;
283        }
284        if end {
285            if self.h264.corrupted || !self.has_decoder_config(Codec::H264) {
286                self.reset_fragment(Codec::H264);
287                return Ok(None);
288            }
289            let data = self.h264.data.clone();
290            let frame =
291                self.frame_with_prefix(&data, self.h264.timestamp, nal_type == 5, Codec::H264);
292            self.reset_fragment(Codec::H264);
293            Ok(Some(frame))
294        } else {
295            Ok(None)
296        }
297    }
298
299    fn h265_fu(
300        &mut self,
301        payload: &[u8],
302        header: RtpHeader,
303    ) -> Result<Option<DepacketizedFrame>, RtpError> {
304        if payload.len() < 3 {
305            return Err(RtpError::UnsupportedPayload);
306        }
307        let fu_header = payload[2];
308        let start = fu_header & 0x80 != 0;
309        let end = fu_header & 0x40 != 0;
310        let nal_type = fu_header & 0x3f;
311        if start {
312            self.h265.data.clear();
313            self.h265.timestamp = header.timestamp;
314            self.h265.next_sequence = Some(header.sequence_number.wrapping_add(1));
315            self.h265.corrupted = false;
316            self.h265.data.push((nal_type << 1) | (payload[0] & 0x01));
317            self.h265.data.push(payload[1]);
318        } else if !self.accept_fragment_sequence(Codec::H265, header.sequence_number) {
319            return Ok(None);
320        }
321        if !self.h265.corrupted {
322            self.append_fragment(Codec::H265, &payload[3..])?;
323        }
324        if end {
325            if self.h265.corrupted || !self.has_decoder_config(Codec::H265) {
326                self.reset_fragment(Codec::H265);
327                return Ok(None);
328            }
329            let data = self.h265.data.clone();
330            let frame = self.frame_with_prefix(
331                &data,
332                self.h265.timestamp,
333                (16..=23).contains(&nal_type),
334                Codec::H265,
335            );
336            self.reset_fragment(Codec::H265);
337            Ok(Some(frame))
338        } else {
339            Ok(None)
340        }
341    }
342
343    fn accept_fragment_sequence(&mut self, codec: Codec, sequence_number: u16) -> bool {
344        let state = match codec {
345            Codec::H264 => &mut self.h264,
346            Codec::H265 => &mut self.h265,
347        };
348        let Some(expected) = state.next_sequence else {
349            return false;
350        };
351        state.next_sequence = Some(sequence_number.wrapping_add(1));
352        if sequence_number != expected {
353            state.data.clear();
354            state.corrupted = true;
355            return false;
356        }
357        true
358    }
359
360    fn reset_fragment(&mut self, codec: Codec) {
361        let state = match codec {
362            Codec::H264 => &mut self.h264,
363            Codec::H265 => &mut self.h265,
364        };
365        state.data.clear();
366        state.next_sequence = None;
367        state.corrupted = false;
368    }
369
370    fn h264_stap_a(
371        &mut self,
372        payload: &[u8],
373        timestamp: u32,
374    ) -> Result<Option<DepacketizedFrame>, RtpError> {
375        let mut out = Vec::new();
376        let mut offset = 1;
377        let mut keyframe = false;
378        let mut has_slice = false;
379        while offset + 2 <= payload.len() {
380            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
381            offset += 2;
382            if offset + len > payload.len() {
383                break;
384            }
385            let nalu = &payload[offset..offset + len];
386            let nal_type = nalu.first().map(|b| b & 0x1f).unwrap_or(0);
387            match nal_type {
388                7 => self.h264_sps = Some(nalu.to_vec()),
389                8 => self.h264_pps = Some(nalu.to_vec()),
390                _ => {}
391            }
392            has_slice |= (1..=5).contains(&nal_type);
393            keyframe |= nal_type == 5;
394            append_annex_b(&mut out, nalu);
395            offset += len;
396        }
397        if !has_slice || !self.has_decoder_config(Codec::H264) {
398            return Ok(None);
399        }
400        Ok(Some(DepacketizedFrame {
401            data: out,
402            timestamp,
403            is_keyframe: keyframe,
404            codec: Codec::H264,
405        }))
406    }
407
408    fn h265_ap(
409        &mut self,
410        payload: &[u8],
411        timestamp: u32,
412    ) -> Result<Option<DepacketizedFrame>, RtpError> {
413        let mut out = Vec::new();
414        let mut offset = 2;
415        let mut keyframe = false;
416        let mut has_slice = false;
417        while offset + 2 <= payload.len() {
418            let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
419            offset += 2;
420            if offset + len > payload.len() {
421                break;
422            }
423            let nalu = &payload[offset..offset + len];
424            let nal_type = nalu.first().map(|b| (b >> 1) & 0x3f).unwrap_or(0);
425            match nal_type {
426                32 => self.h265_vps = Some(nalu.to_vec()),
427                33 => self.h265_sps = Some(nalu.to_vec()),
428                34 => self.h265_pps = Some(nalu.to_vec()),
429                _ => {}
430            }
431            has_slice |= !nalu.is_empty() && nal_type <= 31;
432            keyframe |= (16..=23).contains(&nal_type);
433            append_annex_b(&mut out, nalu);
434            offset += len;
435        }
436        if !has_slice || !self.has_decoder_config(Codec::H265) {
437            return Ok(None);
438        }
439        Ok(Some(DepacketizedFrame {
440            data: out,
441            timestamp,
442            is_keyframe: keyframe,
443            codec: Codec::H265,
444        }))
445    }
446
447    fn append_fragment(&mut self, codec: Codec, bytes: &[u8]) -> Result<(), RtpError> {
448        let state = match codec {
449            Codec::H264 => &mut self.h264,
450            Codec::H265 => &mut self.h265,
451        };
452        if state.data.len() + bytes.len() > self.max_fragment {
453            return Err(RtpError::FragmentOverflow);
454        }
455        state.data.extend_from_slice(bytes);
456        Ok(())
457    }
458
459    fn frame_with_prefix(
460        &self,
461        nalu: &[u8],
462        timestamp: u32,
463        is_keyframe: bool,
464        codec: Codec,
465    ) -> DepacketizedFrame {
466        let mut data = Vec::new();
467        if is_keyframe {
468            match codec {
469                Codec::H264 => {
470                    if let Some(sps) = &self.h264_sps {
471                        append_annex_b(&mut data, sps);
472                    }
473                    if let Some(pps) = &self.h264_pps {
474                        append_annex_b(&mut data, pps);
475                    }
476                }
477                Codec::H265 => {
478                    if let Some(vps) = &self.h265_vps {
479                        append_annex_b(&mut data, vps);
480                    }
481                    if let Some(sps) = &self.h265_sps {
482                        append_annex_b(&mut data, sps);
483                    }
484                    if let Some(pps) = &self.h265_pps {
485                        append_annex_b(&mut data, pps);
486                    }
487                }
488            }
489        }
490        append_annex_b(&mut data, nalu);
491        DepacketizedFrame {
492            data,
493            timestamp,
494            is_keyframe,
495            codec,
496        }
497    }
498
499    fn has_decoder_config(&self, codec: Codec) -> bool {
500        match codec {
501            Codec::H264 => self.h264_sps.is_some() && self.h264_pps.is_some(),
502            Codec::H265 => {
503                self.h265_vps.is_some() && self.h265_sps.is_some() && self.h265_pps.is_some()
504            }
505        }
506    }
507}
508
509fn codec_from_payload_type(payload_type: u8) -> Option<Codec> {
510    match payload_type {
511        RTP_PAYLOAD_TYPE_H264 => Some(Codec::H264),
512        RTP_PAYLOAD_TYPE_H265 => Some(Codec::H265),
513        _ => None,
514    }
515}
516
517fn detect_codec(payload: &[u8]) -> Option<Codec> {
518    if payload.is_empty() {
519        return None;
520    }
521    if payload.len() >= 2 {
522        let h265_nal_type = (payload[0] >> 1) & 0x3f;
523        if h265_nal_type == 48 || h265_nal_type == 49 || (32..=40).contains(&h265_nal_type) {
524            return Some(Codec::H265);
525        }
526    }
527    let h264_nal_type = payload[0] & 0x1f;
528    if h264_nal_type == 24 || h264_nal_type == 28 || (1..=12).contains(&h264_nal_type) {
529        return Some(Codec::H264);
530    }
531    None
532}
533
534fn append_annex_b(out: &mut Vec<u8>, nalu: &[u8]) {
535    out.extend_from_slice(&[0, 0, 0, 1]);
536    out.extend_from_slice(nalu);
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542
543    fn rtp(payload: &[u8], marker: bool, seq: u16, timestamp: u32) -> Vec<u8> {
544        rtp_with_payload_type(payload, RTP_PAYLOAD_TYPE_H264, marker, seq, timestamp)
545    }
546
547    fn rtp_with_payload_type(
548        payload: &[u8],
549        payload_type: u8,
550        marker: bool,
551        seq: u16,
552        timestamp: u32,
553    ) -> Vec<u8> {
554        let mut packet = vec![
555            0x80,
556            (if marker { 0x80 } else { 0x00 }) | (payload_type & 0x7f),
557        ];
558        packet.extend_from_slice(&seq.to_be_bytes());
559        packet.extend_from_slice(&timestamp.to_be_bytes());
560        packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
561        packet.extend_from_slice(payload);
562        packet
563    }
564
565    fn stap_a(units: &[&[u8]]) -> Vec<u8> {
566        let mut payload = vec![24];
567        for unit in units {
568            payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
569            payload.extend_from_slice(unit);
570        }
571        payload
572    }
573
574    fn prime_h264(depay: &mut RtpDepacketizer) {
575        assert!(depay
576            .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
577            .unwrap()
578            .is_none());
579        assert!(depay
580            .push(&rtp(&[0x68, 0xee], true, 2, 10))
581            .unwrap()
582            .is_none());
583    }
584
585    fn prime_h265(depay: &mut RtpDepacketizer) {
586        for (seq, payload) in [
587            (1, &[0x40, 0x01, 0xaa][..]),
588            (2, &[0x42, 0x01, 0xbb][..]),
589            (3, &[0x44, 0x01, 0xcc][..]),
590        ] {
591            assert!(depay
592                .push(&rtp_with_payload_type(
593                    payload,
594                    RTP_PAYLOAD_TYPE_H265,
595                    true,
596                    seq,
597                    10,
598                ))
599                .unwrap()
600                .is_none());
601        }
602    }
603
604    #[test]
605    fn parses_rtp_header() {
606        let packet = rtp(&[0x65, 1, 2], true, 7, 1234);
607        let header = RtpHeader::parse(&packet).unwrap();
608        assert!(header.marker);
609        assert_eq!(header.payload_type, 96);
610        assert_eq!(header.sequence_number, 7);
611        assert_eq!(header.timestamp, 1234);
612        assert_eq!(header.payload(&packet), &[0x65, 1, 2]);
613    }
614
615    #[test]
616    fn depacketizes_h264_single_nalu_as_annex_b() {
617        let mut depay = RtpDepacketizer::new();
618        prime_h264(&mut depay);
619        let frame = depay
620            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
621            .unwrap()
622            .unwrap();
623        assert_eq!(frame.codec, Codec::H264);
624        assert!(frame.is_keyframe);
625        assert_eq!(
626            frame.data,
627            [
628                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
629                &[0, 0, 0, 1, 0x68, 0xee][..],
630                &[0, 0, 0, 1, 0x65, 0xaa][..],
631            ]
632            .concat()
633        );
634    }
635
636    #[test]
637    fn drops_h264_video_until_sps_and_pps_are_seen() {
638        let mut depay = RtpDepacketizer::new();
639        assert!(depay
640            .push(&rtp(&[0x65, 0xaa], true, 1, 42))
641            .unwrap()
642            .is_none());
643    }
644
645    #[test]
646    fn h264_payload_type_prevents_h265_false_positive() {
647        let mut depay = RtpDepacketizer::new();
648        prime_h264(&mut depay);
649        let frame = depay
650            .push(&rtp(&[0x41, 0xaa], true, 1, 42))
651            .unwrap()
652            .unwrap();
653        assert_eq!(frame.codec, Codec::H264);
654        assert!(!frame.is_keyframe);
655        assert_eq!(frame.data, &[0, 0, 0, 1, 0x41, 0xaa]);
656    }
657
658    #[test]
659    fn opus_payload_type_is_not_sniffed_as_video() {
660        let mut depay = RtpDepacketizer::new();
661        prime_h264(&mut depay);
662        let err = depay
663            .push(&rtp_with_payload_type(
664                &[0x65, 0xaa],
665                RTP_PAYLOAD_TYPE_OPUS,
666                true,
667                1,
668                42,
669            ))
670            .unwrap_err();
671        assert_eq!(err, RtpError::UnsupportedPayload);
672    }
673
674    #[test]
675    fn depacketizes_h265_single_nalu_by_payload_type() {
676        let mut depay = RtpDepacketizer::new();
677        prime_h265(&mut depay);
678        let frame = depay
679            .push(&rtp_with_payload_type(
680                &[0x02, 0x01, 0xaa],
681                RTP_PAYLOAD_TYPE_H265,
682                true,
683                1,
684                42,
685            ))
686            .unwrap()
687            .unwrap();
688        assert_eq!(frame.codec, Codec::H265);
689        assert!(!frame.is_keyframe);
690        assert_eq!(frame.data, &[0, 0, 0, 1, 0x02, 0x01, 0xaa]);
691    }
692
693    #[test]
694    fn h264_stap_a_caches_parameter_sets_for_later_keyframe() {
695        let mut depay = RtpDepacketizer::new();
696        let sps = &[0x67, 0x64, 0x00, 0x1f][..];
697        let pps = &[0x68, 0xee][..];
698        let aggregate = depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
699        assert!(aggregate.is_none());
700
701        let frame = depay
702            .push(&rtp(&[0x65, 0xaa], true, 2, 20))
703            .unwrap()
704            .unwrap();
705        assert!(frame.is_keyframe);
706        assert_eq!(
707            frame.data,
708            [
709                &[0, 0, 0, 1][..],
710                sps,
711                &[0, 0, 0, 1][..],
712                pps,
713                &[0, 0, 0, 1, 0x65, 0xaa][..],
714            ]
715            .concat()
716        );
717    }
718
719    #[test]
720    fn depacketizes_h264_fu_a() {
721        let mut depay = RtpDepacketizer::new();
722        prime_h264(&mut depay);
723        assert!(depay
724            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 1, 99))
725            .unwrap()
726            .is_none());
727        let frame = depay
728            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 2, 99))
729            .unwrap()
730            .unwrap();
731        assert_eq!(
732            frame.data,
733            [
734                &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
735                &[0, 0, 0, 1, 0x68, 0xee][..],
736                &[0, 0, 0, 1, 0x65, 1, 2, 3, 4][..],
737            ]
738            .concat()
739        );
740    }
741
742    #[test]
743    fn drops_h264_fu_a_after_sequence_gap() {
744        let mut depay = RtpDepacketizer::new();
745        prime_h264(&mut depay);
746        assert!(depay
747            .push(&rtp(&[0x7c, 0x85, 1, 2], false, 10, 99))
748            .unwrap()
749            .is_none());
750        assert!(depay
751            .push(&rtp(&[0x7c, 0x45, 3, 4], true, 12, 99))
752            .unwrap()
753            .is_none());
754
755        assert!(depay
756            .push(&rtp(&[0x7c, 0x85, 5, 6], false, 13, 100))
757            .unwrap()
758            .is_none());
759        let frame = depay
760            .push(&rtp(&[0x7c, 0x45, 7, 8], true, 14, 100))
761            .unwrap()
762            .unwrap();
763        assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 5, 6, 7, 8]));
764    }
765
766    #[test]
767    fn drops_fragment_end_without_start() {
768        let mut depay = RtpDepacketizer::new();
769        prime_h264(&mut depay);
770        assert!(depay
771            .push(&rtp(&[0x7c, 0x45, 1, 2], true, 10, 99))
772            .unwrap()
773            .is_none());
774    }
775}