Skip to main content

str0m/packet/
h264.rs

1#![allow(clippy::all)]
2
3use super::{CodecExtra, Depacketizer, PacketError, Packetizer};
4
5/// H264 information describing the depacketized / packetized data
6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
7pub struct H264CodecExtra {
8    /// Flag which indicates that within [`MediaData`], there is an individual frame
9    /// containing complete and independent visual information. This frame serves
10    /// as a reference point for other frames in the video sequence.
11    ///
12    /// [`MediaData`]: crate::media::MediaData
13    pub is_keyframe: bool,
14}
15
16/// Packetizes H264 RTP packets.
17///
18/// ## Unversioned API surface
19///
20/// This struct is not currently versioned according to semver rules.
21/// Breaking changes may be made in minor or patch releases.
22#[derive(Default, Debug, Clone)]
23pub struct H264Packetizer {
24    sps_nalu: Option<Vec<u8>>,
25    pps_nalu: Option<Vec<u8>>,
26}
27
28pub const STAPA_NALU_TYPE: u8 = 24;
29pub const FUA_NALU_TYPE: u8 = 28;
30pub const FUB_NALU_TYPE: u8 = 29;
31pub const IDR_NALU_TYPE: u8 = 5;
32pub const SPS_NALU_TYPE: u8 = 7;
33pub const PPS_NALU_TYPE: u8 = 8;
34pub const AUD_NALU_TYPE: u8 = 9;
35pub const FILLER_NALU_TYPE: u8 = 12;
36
37pub const FUA_HEADER_SIZE: usize = 2;
38pub const STAPA_HEADER_SIZE: usize = 1;
39pub const STAPA_NALU_LENGTH_SIZE: usize = 2;
40
41pub const NALU_TYPE_BITMASK: u8 = 0x1F;
42pub const NALU_REF_IDC_BITMASK: u8 = 0x60;
43pub const FU_START_BITMASK: u8 = 0x80;
44pub const FU_END_BITMASK: u8 = 0x40;
45
46pub const OUTPUT_STAP_AHEADER: u8 = 0x78;
47
48pub static ANNEXB_NALUSTART_CODE: &[u8] = &[0x00, 0x00, 0x00, 0x01];
49
50/// Detect whether an H264 RTP payload contains a keyframe.
51///
52/// Checks for IDR (Instantaneous Decoding Refresh) NAL units (type 5)
53/// in the RTP payload. Handles single NAL units, STAP-A aggregation
54/// packets, and FU-A fragmentation units.
55///
56/// For FU-A packets, only the start fragment (S=1) is detected as a
57/// keyframe since the NAL type is only reliably available there.
58pub fn detect_h264_keyframe(payload: &[u8]) -> bool {
59    if payload.is_empty() {
60        return false;
61    }
62
63    let nalu_type = payload[0] & NALU_TYPE_BITMASK;
64
65    match nalu_type {
66        // Single NAL unit (types 1-23)
67        1..=23 => nalu_type == IDR_NALU_TYPE,
68
69        // STAP-A: check all aggregated NALUs
70        STAPA_NALU_TYPE => {
71            let mut offset = STAPA_HEADER_SIZE;
72            while offset + STAPA_NALU_LENGTH_SIZE <= payload.len() {
73                let nalu_size = ((payload[offset] as usize) << 8) | payload[offset + 1] as usize;
74                offset += STAPA_NALU_LENGTH_SIZE;
75                if offset + nalu_size > payload.len() {
76                    break;
77                }
78                if let Some(&b0) = payload.get(offset) {
79                    if b0 & NALU_TYPE_BITMASK == IDR_NALU_TYPE {
80                        return true;
81                    }
82                }
83                offset += nalu_size;
84            }
85            false
86        }
87
88        // FU-A: check start fragment for IDR type
89        FUA_NALU_TYPE => {
90            if payload.len() < FUA_HEADER_SIZE {
91                return false;
92            }
93            let b1 = payload[1];
94            // Only the start fragment (S=1) reliably carries the original NAL type
95            if b1 & FU_START_BITMASK == 0 {
96                return false;
97            }
98            b1 & NALU_TYPE_BITMASK == IDR_NALU_TYPE
99        }
100
101        _ => false,
102    }
103}
104
105impl H264Packetizer {
106    fn next_ind(nalu: &[u8], start: usize) -> (isize, isize) {
107        let mut zero_count = 0;
108
109        for (i, &b) in nalu[start..].iter().enumerate() {
110            if b == 0 {
111                zero_count += 1;
112                continue;
113            } else if b == 1 && zero_count >= 2 {
114                return ((start + i - zero_count) as isize, zero_count as isize + 1);
115            }
116            zero_count = 0
117        }
118        (-1, -1)
119    }
120
121    fn emit(&mut self, nalu: &[u8], mtu: usize, payloads: &mut Vec<Vec<u8>>) {
122        if nalu.is_empty() {
123            return;
124        }
125
126        let nalu_type = nalu[0] & NALU_TYPE_BITMASK;
127        let nalu_ref_idc = nalu[0] & NALU_REF_IDC_BITMASK;
128
129        if nalu_type == AUD_NALU_TYPE || nalu_type == FILLER_NALU_TYPE {
130            return;
131        } else if nalu_type == SPS_NALU_TYPE {
132            self.sps_nalu = Some(nalu.to_vec());
133            return;
134        } else if nalu_type == PPS_NALU_TYPE {
135            self.pps_nalu = Some(nalu.to_vec());
136            return;
137        } else if let (Some(sps_nalu), Some(pps_nalu)) = (&self.sps_nalu, &self.pps_nalu) {
138            // Pack current NALU with SPS and PPS as STAP-A
139            let sps_len = (sps_nalu.len() as u16).to_be_bytes();
140            let pps_len = (pps_nalu.len() as u16).to_be_bytes();
141
142            let mut stap_a_nalu = Vec::with_capacity(1 + 2 + sps_nalu.len() + 2 + pps_nalu.len());
143            stap_a_nalu.push(OUTPUT_STAP_AHEADER);
144            stap_a_nalu.extend(sps_len);
145            stap_a_nalu.extend_from_slice(sps_nalu);
146            stap_a_nalu.extend(pps_len);
147            stap_a_nalu.extend_from_slice(pps_nalu);
148            if stap_a_nalu.len() <= mtu {
149                payloads.push(stap_a_nalu);
150            }
151        }
152
153        if self.sps_nalu.is_some() && self.pps_nalu.is_some() {
154            self.sps_nalu = None;
155            self.pps_nalu = None;
156        }
157
158        // Single NALU
159        if nalu.len() <= mtu {
160            payloads.push(nalu.to_vec());
161            return;
162        }
163
164        // FU-A
165        let max_fragment_size = mtu as isize - FUA_HEADER_SIZE as isize;
166
167        // The FU payload consists of fragments of the payload of the fragmented
168        // NAL unit so that if the fragmentation unit payloads of consecutive
169        // FUs are sequentially concatenated, the payload of the fragmented NAL
170        // unit can be reconstructed.  The NAL unit type octet of the fragmented
171        // NAL unit is not included as such in the fragmentation unit payload,
172        // 	but rather the information of the NAL unit type octet of the
173        // fragmented NAL unit is conveyed in the F and NRI fields of the FU
174        // indicator octet of the fragmentation unit and in the type field of
175        // the FU header.  An FU payload MAY have any number of octets and MAY
176        // be empty.
177
178        let nalu_data = nalu;
179        // According to the RFC, the first octet is skipped due to redundant information
180        let mut nalu_data_index = 1;
181        let nalu_data_length = nalu.len() as isize - nalu_data_index;
182        let mut nalu_data_remaining = nalu_data_length;
183
184        if std::cmp::min(max_fragment_size, nalu_data_remaining) <= 0 {
185            return;
186        }
187
188        while nalu_data_remaining > 0 {
189            let current_fragment_size = std::cmp::min(max_fragment_size, nalu_data_remaining);
190            //out: = make([]byte, fuaHeaderSize + currentFragmentSize)
191            let mut out = Vec::with_capacity(FUA_HEADER_SIZE + current_fragment_size as usize);
192            // +---------------+
193            // |0|1|2|3|4|5|6|7|
194            // +-+-+-+-+-+-+-+-+
195            // |F|NRI|  Type   |
196            // +---------------+
197            let b0 = FUA_NALU_TYPE | nalu_ref_idc;
198            out.push(b0);
199
200            // +---------------+
201            //|0|1|2|3|4|5|6|7|
202            //+-+-+-+-+-+-+-+-+
203            //|S|E|R|  Type   |
204            //+---------------+
205
206            let mut b1 = nalu_type;
207            if nalu_data_remaining == nalu_data_length {
208                // Set start bit
209                b1 |= 1 << 7;
210            } else if nalu_data_remaining - current_fragment_size == 0 {
211                // Set end bit
212                b1 |= 1 << 6;
213            }
214            out.push(b1);
215
216            out.extend_from_slice(
217                &nalu_data
218                    [nalu_data_index as usize..(nalu_data_index + current_fragment_size) as usize],
219            );
220            payloads.push(out);
221
222            nalu_data_remaining -= current_fragment_size;
223            nalu_data_index += current_fragment_size;
224        }
225    }
226}
227
228impl Packetizer for H264Packetizer {
229    /// Payload fragments a H264 packet across one or more byte arrays
230    fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
231        if payload.is_empty() || mtu == 0 {
232            return Ok(vec![]);
233        }
234
235        let mut payloads = vec![];
236
237        let (mut next_ind_start, mut next_ind_len) = H264Packetizer::next_ind(payload, 0);
238        if next_ind_start == -1 {
239            self.emit(payload, mtu, &mut payloads);
240        } else {
241            while next_ind_start != -1 {
242                let prev_start = (next_ind_start + next_ind_len) as usize;
243                let (next_ind_start2, next_ind_len2) =
244                    H264Packetizer::next_ind(payload, prev_start);
245                next_ind_start = next_ind_start2;
246                next_ind_len = next_ind_len2;
247                if next_ind_start != -1 {
248                    self.emit(
249                        &payload[prev_start..next_ind_start as usize],
250                        mtu,
251                        &mut payloads,
252                    );
253                } else {
254                    // Emit until end of stream, no end indicator found
255                    self.emit(&payload[prev_start..], mtu, &mut payloads);
256                }
257            }
258        }
259
260        Ok(payloads)
261    }
262
263    fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
264        last
265    }
266}
267
268/// Depacketizes H264 RTP packets.
269///
270/// ## Unversioned API surface
271///
272/// This struct is not currently versioned according to semver rules.
273/// Breaking changes may be made in minor or patch releases.
274#[derive(PartialEq, Eq, Debug, Default, Clone)]
275pub struct H264Depacketizer {
276    /// Whether to output in AVC format (length-prefixed NALUs) instead of Annex B format
277    pub is_avc: bool,
278    fua_buffer: Option<Vec<u8>>,
279}
280
281impl Depacketizer for H264Depacketizer {
282    fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
283        // Roughly account for Annex B start codes or AVC length prefixes.
284        let estimated_packets = (packets_size / 1200).saturating_add(1);
285        Some(packets_size.saturating_add(4usize.saturating_mul(estimated_packets)))
286    }
287
288    /// depacketize parses the passed byte slice and stores the result in the
289    /// H264Packet this method is called upon
290    fn depacketize(
291        &mut self,
292        packet: &[u8],
293        out: &mut Vec<u8>,
294        extra: &mut CodecExtra,
295    ) -> Result<(), PacketError> {
296        if packet.len() == 0 {
297            return Err(PacketError::ErrShortPacket);
298        }
299
300        // NALU Types
301        // https://tools.ietf.org/html/rfc6184#section-5.4
302        let b0 = packet[0];
303        let nalu_type = b0 & NALU_TYPE_BITMASK;
304
305        match nalu_type {
306            t @ 1..=23 => {
307                let is_keyframe = if let CodecExtra::H264(e) = extra {
308                    (t == IDR_NALU_TYPE) | e.is_keyframe
309                } else {
310                    t == IDR_NALU_TYPE
311                };
312                *extra = CodecExtra::H264(H264CodecExtra { is_keyframe });
313
314                if self.is_avc {
315                    out.extend_from_slice(&(packet.len() as u32).to_be_bytes());
316                } else {
317                    out.extend_from_slice(ANNEXB_NALUSTART_CODE);
318                }
319                out.extend_from_slice(packet);
320                Ok(())
321            }
322            STAPA_NALU_TYPE => {
323                let mut curr_offset = STAPA_HEADER_SIZE;
324                while curr_offset + 1 < packet.len() {
325                    let nalu_size =
326                        ((packet[curr_offset] as usize) << 8) | packet[curr_offset + 1] as usize;
327                    curr_offset += STAPA_NALU_LENGTH_SIZE;
328
329                    if curr_offset + nalu_size > packet.len() {
330                        return Err(PacketError::StapASizeLargerThanBuffer(
331                            nalu_size,
332                            packet.len() - curr_offset,
333                        ));
334                    }
335
336                    let Some(b0) = packet.get(curr_offset) else {
337                        continue;
338                    };
339                    let t = b0 & NALU_TYPE_BITMASK;
340                    let is_keyframe = if let CodecExtra::H264(e) = extra {
341                        (t == IDR_NALU_TYPE) | e.is_keyframe
342                    } else {
343                        t == IDR_NALU_TYPE
344                    };
345                    *extra = CodecExtra::H264(H264CodecExtra { is_keyframe });
346
347                    if self.is_avc {
348                        out.extend_from_slice(&(nalu_size as u32).to_be_bytes());
349                    } else {
350                        out.extend_from_slice(ANNEXB_NALUSTART_CODE);
351                    }
352                    out.extend_from_slice(&packet[curr_offset..curr_offset + nalu_size]);
353                    curr_offset += nalu_size;
354                }
355
356                Ok(())
357            }
358            FUA_NALU_TYPE => {
359                if packet.len() < FUA_HEADER_SIZE as usize {
360                    return Err(PacketError::ErrShortPacket);
361                }
362
363                if self.fua_buffer.is_none() {
364                    self.fua_buffer = Some(Vec::new());
365                }
366
367                if let Some(fua_buffer) = &mut self.fua_buffer {
368                    fua_buffer.extend_from_slice(&packet[FUA_HEADER_SIZE as usize..]);
369                }
370
371                let b1 = packet[1];
372                if b1 & FU_END_BITMASK != 0 {
373                    let nalu_ref_idc = b0 & NALU_REF_IDC_BITMASK;
374                    let fragmented_nalu_type = b1 & NALU_TYPE_BITMASK;
375
376                    let is_keyframe = if let CodecExtra::H264(e) = extra {
377                        (fragmented_nalu_type == IDR_NALU_TYPE) | e.is_keyframe
378                    } else {
379                        fragmented_nalu_type == IDR_NALU_TYPE
380                    };
381                    *extra = CodecExtra::H264(H264CodecExtra { is_keyframe });
382
383                    if let Some(fua_buffer) = self.fua_buffer.take() {
384                        if self.is_avc {
385                            out.extend_from_slice(&((fua_buffer.len() + 1) as u32).to_be_bytes());
386                        } else {
387                            out.extend_from_slice(ANNEXB_NALUSTART_CODE);
388                        }
389                        out.push(nalu_ref_idc | fragmented_nalu_type);
390                        out.extend_from_slice(&fua_buffer);
391                    }
392
393                    Ok(())
394                } else {
395                    Ok(())
396                }
397            }
398            _ => Err(PacketError::NaluTypeIsNotHandled(nalu_type)),
399        }
400    }
401
402    /// is_partition_head checks if this is the head of a packetized nalu stream.
403    fn is_partition_head(&self, packet: &[u8]) -> bool {
404        if packet.len() < 2 {
405            return false;
406        }
407
408        if packet[0] & NALU_TYPE_BITMASK == FUA_NALU_TYPE
409            || packet[0] & NALU_TYPE_BITMASK == FUB_NALU_TYPE
410        {
411            (packet[1] & FU_START_BITMASK) != 0
412        } else {
413            true
414        }
415    }
416
417    fn is_partition_tail(&self, marker: bool, _packet: &[u8]) -> bool {
418        marker
419    }
420}
421
422#[cfg(test)]
423mod test {
424    use super::*;
425
426    #[test]
427    fn test_h264_payload() -> Result<(), PacketError> {
428        let empty = &[];
429        let small_payload = &[0x90, 0x90, 0x90];
430        let multiple_payload = &[0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90];
431        let large_payload = &[
432            0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
433            0x11, 0x12, 0x13, 0x14, 0x15,
434        ];
435        let large_payload_packetized = vec![
436            &[0x1c, 0x80, 0x01, 0x02, 0x03],
437            &[0x1c, 0x00, 0x04, 0x05, 0x06],
438            &[0x1c, 0x00, 0x07, 0x08, 0x09],
439            &[0x1c, 0x00, 0x10, 0x11, 0x12],
440            &[0x1c, 0x40, 0x13, 0x14, 0x15],
441        ];
442
443        let mut pck = H264Packetizer::default();
444
445        // Positive MTU, empty payload
446        let result = pck.packetize(1, empty)?;
447        assert!(result.is_empty(), "Generated payload should be empty");
448
449        // 0 MTU, small payload
450        let result = pck.packetize(0, small_payload)?;
451        assert_eq!(result.len(), 0, "Generated payload should be empty");
452
453        // Positive MTU, small payload
454        let result = pck.packetize(1, small_payload)?;
455        assert_eq!(result.len(), 0, "Generated payload should be empty");
456
457        // Positive MTU, small payload
458        let result = pck.packetize(5, small_payload)?;
459        assert_eq!(result.len(), 1, "Generated payload should be the 1");
460        assert_eq!(
461            result[0].len(),
462            small_payload.len(),
463            "Generated payload should be the same size as original payload size"
464        );
465
466        // Multiple NALU in a single payload
467        let result = pck.packetize(5, multiple_payload)?;
468        assert_eq!(result.len(), 2, "2 nal units should be broken out");
469        for i in 0..2 {
470            assert_eq!(
471                result[i].len(),
472                1,
473                "Payload {} of 2 is packed incorrectly",
474                i + 1,
475            );
476        }
477
478        // Large Payload split across multiple RTP Packets
479        let result = pck.packetize(5, large_payload)?;
480        assert_eq!(
481            result, large_payload_packetized,
482            "FU-A packetization failed"
483        );
484
485        // Nalu type 9 or 12
486        let small_payload2 = &[0x09, 0x00, 0x00];
487        let result = pck.packetize(5, small_payload2)?;
488        assert_eq!(result.len(), 0, "Generated payload should be empty");
489
490        Ok(())
491    }
492
493    macro_rules! test_h264 {
494        ($name:tt, $is_avc:expr, $is_ok: expr, $payload:expr, $err:tt) => {
495            #[test]
496            fn $name() -> Result<(), PacketError> {
497                let mut pkt = H264Depacketizer::default();
498                pkt.is_avc = $is_avc;
499                let mut extra = CodecExtra::None;
500                let mut out: Vec<u8> = Vec::new();
501                let result = pkt.depacketize($payload, &mut out, &mut extra);
502                if $is_ok {
503                    assert!(result.is_ok(), $err);
504                } else {
505                    assert!(result.is_err(), $err);
506                }
507                Ok(())
508            }
509        };
510    }
511
512    test_h264!(
513        nil_payload,
514        false,
515        false,
516        &[],
517        "Unmarshal did not fail on nil payload"
518    );
519    test_h264!(
520        unit_delimiter,
521        false,
522        true,
523        &[0x09, 0x30],
524        "Unmarshal should accept minimal h.264 access unit delimiter"
525    );
526    test_h264!(
527        end_of_sequence_nalu,
528        false,
529        true,
530        &[0x0A],
531        "Unmarshal should accept end of sequence NALU"
532    );
533    test_h264!(
534        not_handled,
535        false,
536        false,
537        &[0xFF, 0x00, 0x00],
538        "Unmarshal accepted a packet with a NALU Type we don't handle"
539    );
540    test_h264!(
541        incomplete_single_payload_multi_nalu,
542        false,
543        false,
544        &[
545            0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40,
546            0x3c, 0x22, 0x11,
547        ],
548        "Unmarshal accepted a STAP-A packet with insufficient data"
549    );
550
551    #[test]
552    fn single_payload() -> Result<(), PacketError> {
553        let mut pkt = H264Depacketizer::default();
554        let mut extra = CodecExtra::None;
555        let mut out: Vec<u8> = Vec::new();
556        let single_payload = &[0x90, 0x90, 0x90];
557        let _ = pkt.depacketize(single_payload, &mut out, &mut extra);
558        let single_payload_unmarshaled = &[0x00, 0x00, 0x00, 0x01, 0x90, 0x90, 0x90];
559        assert_eq!(
560            out, single_payload_unmarshaled,
561            "Unmarshaling a single payload shouldn't modify the payload"
562        );
563        Ok(())
564    }
565
566    #[test]
567    fn single_payload_avc() -> Result<(), PacketError> {
568        let mut pkt = H264Depacketizer::default();
569        pkt.is_avc = true;
570        let mut extra = CodecExtra::None;
571        let mut out: Vec<u8> = Vec::new();
572        let single_payload = &[0x90, 0x90, 0x90];
573        let _ = pkt.depacketize(single_payload, &mut out, &mut extra);
574        let single_payload_unmarshaled_avc = &[0x00, 0x00, 0x00, 0x03, 0x90, 0x90, 0x90];
575        assert_eq!(
576            out, single_payload_unmarshaled_avc,
577            "Unmarshaling a single payload into avc stream shouldn't modify the payload"
578        );
579        Ok(())
580    }
581
582    #[test]
583    fn h264_large_out() -> Result<(), PacketError> {
584        let large_payload_packetized = vec![
585            &[0x1c, 0x80, 0x01, 0x02, 0x03],
586            &[0x1c, 0x00, 0x04, 0x05, 0x06],
587            &[0x1c, 0x00, 0x07, 0x08, 0x09],
588            &[0x1c, 0x00, 0x10, 0x11, 0x12],
589            &[0x1c, 0x40, 0x13, 0x14, 0x15],
590        ];
591
592        let large_payload = &[
593            0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
594            0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
595        ];
596
597        let mut pkt = H264Depacketizer::default();
598        let mut extra = CodecExtra::None;
599
600        let mut large_out = Vec::new();
601        for p in &large_payload_packetized {
602            pkt.depacketize(*p, &mut large_out, &mut extra)?;
603        }
604        assert_eq!(
605            large_out, large_payload,
606            "Failed to unmarshal a large payload"
607        );
608
609        Ok(())
610    }
611
612    #[test]
613    fn h264_large_out_avc() -> Result<(), PacketError> {
614        let large_payload_packetized = vec![
615            &[0x1c, 0x80, 0x01, 0x02, 0x03],
616            &[0x1c, 0x00, 0x04, 0x05, 0x06],
617            &[0x1c, 0x00, 0x07, 0x08, 0x09],
618            &[0x1c, 0x00, 0x10, 0x11, 0x12],
619            &[0x1c, 0x40, 0x13, 0x14, 0x15],
620        ];
621
622        let large_payload_avc = &[
623            0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
624            0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
625        ];
626
627        let mut avc_pkt = H264Depacketizer {
628            is_avc: true,
629            ..Default::default()
630        };
631
632        let mut extra = CodecExtra::None;
633
634        let mut large_out_avc = Vec::new();
635        for p in &large_payload_packetized {
636            avc_pkt.depacketize(*p, &mut large_out_avc, &mut extra)?;
637        }
638        assert_eq!(
639            large_out_avc, large_payload_avc,
640            "Failed to unmarshal a large payload into avc stream"
641        );
642
643        Ok(())
644    }
645
646    #[test]
647    fn single_payload_multi_nalu() -> Result<(), PacketError> {
648        let single_payload_multi_nalu = &[
649            0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40,
650            0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 0x00,
651        ];
652        let single_payload_multi_nalu_unmarshaled = &[
653            0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a,
654            0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x68, 0x1a, 0x34, 0xe3, 0xc8,
655        ];
656
657        let mut pkt = H264Depacketizer::default();
658
659        let mut extra = CodecExtra::None;
660
661        let mut out = Vec::new();
662        pkt.depacketize(single_payload_multi_nalu, &mut out, &mut extra)?;
663        assert_eq!(
664            out, single_payload_multi_nalu_unmarshaled,
665            "Failed to unmarshal a single packet with multiple NALUs"
666        );
667
668        Ok(())
669    }
670
671    #[test]
672    fn single_payload_multi_nalu_avc() -> Result<(), PacketError> {
673        let single_payload_multi_nalu = &[
674            0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40,
675            0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 0x00,
676        ];
677        let single_payload_multi_nalu_unmarshaled_avc = &[
678            0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a,
679            0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8,
680        ];
681
682        let mut avc_pkt = H264Depacketizer::default();
683        avc_pkt.is_avc = true;
684
685        let mut extra = CodecExtra::None;
686
687        let mut out = Vec::new();
688        avc_pkt.depacketize(single_payload_multi_nalu, &mut out, &mut extra)?;
689        assert_eq!(
690            out, single_payload_multi_nalu_unmarshaled_avc,
691            "Failed to unmarshal a single packet with multiple NALUs into avc stream"
692        );
693
694        Ok(())
695    }
696
697    #[test]
698    fn test_h264_partition_head_checker_is_partition_head() -> Result<(), PacketError> {
699        let h264 = H264Depacketizer::default();
700        let empty_nalu = &[];
701        assert!(
702            !h264.is_partition_head(empty_nalu),
703            "empty nalu must not be a partition head"
704        );
705
706        let single_nalu = &[1, 0];
707        assert!(
708            h264.is_partition_head(single_nalu),
709            "single nalu must be a partition head"
710        );
711
712        let stapa_nalu = &[STAPA_NALU_TYPE, 0];
713        assert!(
714            h264.is_partition_head(stapa_nalu),
715            "stapa nalu must be a partition head"
716        );
717
718        let fua_start_nalu = &[FUA_NALU_TYPE, FU_START_BITMASK];
719        assert!(
720            h264.is_partition_head(fua_start_nalu),
721            "fua start nalu must be a partition head"
722        );
723
724        let fua_end_nalu = &[FUA_NALU_TYPE, FU_END_BITMASK];
725        assert!(
726            !h264.is_partition_head(fua_end_nalu),
727            "fua end nalu must not be a partition head"
728        );
729
730        let fub_start_nalu = &[FUB_NALU_TYPE, FU_START_BITMASK];
731        assert!(
732            h264.is_partition_head(fub_start_nalu),
733            "fub start nalu must be a partition head"
734        );
735
736        let fub_end_nalu = &[FUB_NALU_TYPE, FU_END_BITMASK];
737        assert!(
738            !h264.is_partition_head(fub_end_nalu),
739            "fub end nalu must not be a partition head"
740        );
741
742        Ok(())
743    }
744
745    #[test]
746    fn test_h264_packetizer_payload_sps_and_pps_handling() -> Result<(), PacketError> {
747        let mut pck = H264Packetizer::default();
748        let expected: Vec<&[u8]> = vec![
749            &[
750                0x78, 0x00, 0x03, 0x07, 0x00, 0x01, 0x00, 0x03, 0x08, 0x02, 0x03,
751            ],
752            &[0x05, 0x04, 0x05],
753        ];
754
755        // When packetizing SPS and PPS are emitted with following NALU
756        let res = pck.packetize(1500, &[0x07, 0x00, 0x01])?;
757        assert!(res.is_empty(), "Generated payload should be empty");
758
759        let res = pck.packetize(1500, &[0x08, 0x02, 0x03])?;
760        assert!(res.is_empty(), "Generated payload should be empty");
761
762        let actual = pck.packetize(1500, &[0x05, 0x04, 0x05])?;
763        assert_eq!(actual, expected, "SPS and PPS aren't packed together");
764
765        Ok(())
766    }
767
768    #[test]
769    fn test_h264_depacketizer_idr_handling() -> Result<(), PacketError> {
770        let mut pck = H264Depacketizer::default();
771        let mut extra = CodecExtra::None;
772        let mut out = vec![];
773
774        // First byte is NALU type
775        let packet = [0x85];
776        pck.depacketize(&packet, &mut out, &mut extra)?;
777        let CodecExtra::H264(e) = extra else {
778            panic!("Expected CodecExtra::H264");
779        };
780        assert!(e.is_keyframe);
781
782        // First byte is STAPA NALU type
783        let packet = vec![
784            vec![
785                120, 0, 15, 103, 66, 192, 21, 140, 141, 64, 160, 203, 207, 0, 240, 136, 70, 160, 0,
786                4, 104, 206, 60, 128, 1, 20, 101,
787            ],
788            vec![0; 276],
789        ]
790        .into_iter()
791        .flatten()
792        .collect::<Vec<_>>();
793        pck.depacketize(packet.as_slice(), &mut out, &mut extra)?;
794        let CodecExtra::H264(e) = extra else {
795            panic!("Expected CodecExtra::H264");
796        };
797        assert!(e.is_keyframe);
798
799        // First byte is FUA NALU type
800        let packet = [124, 69];
801        pck.depacketize(&packet, &mut out, &mut extra)?;
802        let CodecExtra::H264(e) = extra else {
803            panic!("Expected CodecExtra::H264");
804        };
805        assert!(e.is_keyframe);
806        Ok(())
807    }
808
809    #[test]
810    fn parse_first_packet() {
811        const PACKET: &[u8] = &[
812            120, 000, 015, 103, 066, 192, 021, 140, 141, 064, 160, 203, 207, 000, 240, 136, 070,
813            160, 000, 004, 104, 206, 060, 128, 000, 204, 101, 184, 000, 004, 000, 000, 005, 057,
814            049, 064, 000, 064, 222, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078,
815            078, 078, 078, 078, 078, 078, 078, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186,
816            235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174,
817            186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 173, 223, 039, 125, 247, 223,
818            125, 245, 215, 093, 117, 215, 093, 117, 214, 239, 174, 187, 235, 174, 186, 235, 174,
819            186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235,
820            174, 186, 235, 174, 183, 093, 117, 215, 093, 117, 215, 093, 117, 215, 093, 117, 215,
821            093, 117, 215, 093, 117, 215, 092, 189, 117, 215, 093, 117, 215, 093, 117, 215, 093,
822            117, 215, 093, 117, 215, 093, 117, 215, 093, 117, 214, 239, 190, 251, 239, 190, 186,
823            235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174,
824            186, 235, 174, 186, 235, 175, 227, 255, 240, 247, 021, 223, 125, 247, 223, 125, 247,
825            223, 125, 247, 223, 125, 247, 223, 125, 248,
826        ];
827
828        let mut pck = H264Depacketizer::default();
829        let mut extra = CodecExtra::None;
830        let mut out = vec![];
831        pck.depacketize(PACKET, &mut out, &mut extra).unwrap();
832    }
833
834    #[test]
835    fn test_out_of_bounds_access() {
836        const PACKET: &[u8] = &[STAPA_NALU_TYPE, 0x00, 0x00];
837
838        let mut pck = H264Depacketizer::default();
839        let mut extra = CodecExtra::None;
840        let mut out = vec![];
841        pck.depacketize(PACKET, &mut out, &mut extra).unwrap();
842    }
843
844    #[test]
845    fn test_detect_h264_keyframe() {
846        // Empty payload
847        assert!(!detect_h264_keyframe(&[]));
848
849        // Single IDR NAL unit (type 5)
850        assert!(detect_h264_keyframe(&[0x65, 0x00, 0x00])); // 0x65 & 0x1F = 5
851
852        // Single non-IDR NAL unit (type 1 = coded slice)
853        assert!(!detect_h264_keyframe(&[0x41, 0x00, 0x00])); // 0x41 & 0x1F = 1
854
855        // SPS (type 7) - not a keyframe
856        assert!(!detect_h264_keyframe(&[0x67, 0x00, 0x00]));
857
858        // PPS (type 8) - not a keyframe
859        assert!(!detect_h264_keyframe(&[0x68, 0x00, 0x00]));
860
861        // STAP-A containing IDR
862        // Header: type 24 (STAP-A)
863        // NALU 1: 2 bytes, type 7 (SPS)
864        // NALU 2: 2 bytes, type 5 (IDR)
865        let stapa_with_idr = [
866            0x18, // STAP-A (24)
867            0x00, 0x02, 0x67, 0xAA, // SPS: size=2, type=7
868            0x00, 0x02, 0x65, 0xBB, // IDR: size=2, type=5
869        ];
870        assert!(detect_h264_keyframe(&stapa_with_idr));
871
872        // STAP-A without IDR
873        let stapa_no_idr = [
874            0x18, // STAP-A (24)
875            0x00, 0x02, 0x67, 0xAA, // SPS: size=2, type=7
876            0x00, 0x02, 0x68, 0xBB, // PPS: size=2, type=8
877        ];
878        assert!(!detect_h264_keyframe(&stapa_no_idr));
879
880        // FU-A start fragment with IDR type
881        let fua_start_idr = [
882            0x7C, // FU indicator: type=28 (FU-A)
883            0x85, // FU header: S=1, type=5 (IDR)
884            0x00, 0x00,
885        ];
886        assert!(detect_h264_keyframe(&fua_start_idr));
887
888        // FU-A start fragment with non-IDR type
889        let fua_start_non_idr = [
890            0x7C, // FU indicator: type=28 (FU-A)
891            0x81, // FU header: S=1, type=1 (non-IDR)
892            0x00, 0x00,
893        ];
894        assert!(!detect_h264_keyframe(&fua_start_non_idr));
895
896        // FU-A continuation fragment (S=0) - cannot detect
897        let fua_continuation = [
898            0x7C, // FU indicator: type=28 (FU-A)
899            0x05, // FU header: S=0, type=5 (IDR) but S=0
900            0x00, 0x00,
901        ];
902        assert!(!detect_h264_keyframe(&fua_continuation));
903
904        // FU-A too short
905        assert!(!detect_h264_keyframe(&[0x7C]));
906    }
907}