src/h264.rs

/// H.264 RTP depacketization and Annex-B bitstream utilities.

pub(crate) const MAX_VIDEO_FRAME_BYTES: usize = 8 * 1024 * 1024;

#[derive(Default)] pub(crate) struct H264Depacketizer { pub(crate) timestamp: Option, pub(crate) buffer: Vec, pub(crate) keyframe: bool, pub(crate) in_fu: bool, /// Cached SPS NAL unit (without start code) from the most recent SPS seen. pub(crate) cached_sps: Option<Vec>, /// Cached PPS NAL unit (without start code) from the most recent PPS seen. pub(crate) cached_pps: Option<Vec>, }

impl H264Depacketizer { pub(crate) fn push( &mut self, timestamp: u32, marker: bool, payload: &[u8], ) -> Option<(Vec, bool)> { if payload.is_empty() { return None; } self.prepare_timestamp(timestamp); let nal_type = payload[0] & 0x1F; match nal_type { 1..=23 => { self.cache_parameter_set(nal_type, payload); self.append_start_code(); self.extend(payload)?; if nal_type == 5 { self.keyframe = true; } self.in_fu = false; } 24 => { let mut cursor = 1usize; while cursor + 2 <= payload.len() { let nalu_len = u16::from_be_bytes([payload[cursor], payload[cursor + 1]]) as usize; cursor += 2; if nalu_len == 0 || cursor + nalu_len > payload.len() { self.reset(); return None; } let nalu = &payload[cursor..cursor + nalu_len]; if !nalu.is_empty() { let stap_nal_type = nalu[0] & 0x1F; self.cache_parameter_set(stap_nal_type, nalu); if stap_nal_type == 5 { self.keyframe = true; } self.append_start_code(); self.extend(nalu)?; } cursor += nalu_len; } } 28 => { if payload.len() < 2 { return None; } let indicator = payload[0]; let fu_header = payload[1]; let start = (fu_header & 0x80) != 0; let nal_type = fu_header & 0x1F; if start { let reconstructed_header = (indicator & 0xE0) | nal_type; self.append_start_code(); self.extend(&[reconstructed_header])?; self.extend(&payload[2..])?; self.in_fu = true; if nal_type == 5 { self.keyframe = true; } } else { if !self.in_fu { return None; } self.extend(&payload[2..])?; if (fu_header & 0x40) != 0 { self.in_fu = false; } } } _ => { return None; } }

    if marker && !self.buffer.is_empty() {
        let keyframe = self.keyframe || h264_annexb_has_idr_slice(&self.buffer);
        let frame = std::mem::take(&mut self.buffer);
        // NOTE: Do NOT prepend cached SPS+PPS here.  The depacketized
        // frame goes to DAVE decrypt first, and prepending would shift
        // the byte offsets that the DAVE trailer's unencrypted ranges
        // reference, causing decrypt to fail.  SPS+PPS prepend happens
        // AFTER DAVE decrypt in the UDP recv loop.
        self.timestamp = None;
        self.keyframe = false;
        self.in_fu = false;
        return Some((frame, keyframe));
    }

    None
}

/// Cache SPS (NAL type 7) and PPS (NAL type 8) NAL units so they can be
/// prepended to keyframes that arrive without inline parameter sets.
pub(crate) fn cache_parameter_set(&mut self, nal_type: u8, nalu: &[u8]) {
    match nal_type {
        7 => {
            self.cached_sps = Some(nalu.to_vec());
        }
        8 => {
            self.cached_pps = Some(nalu.to_vec());
        }
        _ => {}
    }
}

/// If the assembled frame is a keyframe but doesn't contain SPS/PPS inline,
/// prepend the cached parameter sets so ffmpeg can decode it standalone.
pub(crate) fn prepend_cached_parameter_sets(&self, frame: Vec<u8>) -> Vec<u8> {
    let has_sps = Self::annexb_contains_nal_type(&frame, 7);
    let has_pps = Self::annexb_contains_nal_type(&frame, 8);
    if has_sps && has_pps {
        return frame;
    }

    let sps = if !has_sps {
        self.cached_sps.as_deref()
    } else {
        None
    };
    let pps = if !has_pps {
        self.cached_pps.as_deref()
    } else {
        None
    };
    if sps.is_none() && pps.is_none() {
        return frame;
    }

    let start_code: &[u8] = &[0, 0, 0, 1];
    let extra_len = sps.map_or(0, |s| 4 + s.len()) + pps.map_or(0, |p| 4 + p.len());
    let mut out = Vec::with_capacity(extra_len + frame.len());
    if let Some(s) = sps {
        out.extend_from_slice(start_code);
        out.extend_from_slice(s);
    }
    if let Some(p) = pps {
        out.extend_from_slice(start_code);
        out.extend_from_slice(p);
    }
    out.extend_from_slice(&frame);
    out
}

/// Scan an Annex-B bitstream for the presence of a specific NAL type.
pub(crate) fn annexb_contains_nal_type(buf: &[u8], target: u8) -> bool {
    let mut i = 0;
    while i < buf.len().saturating_sub(3) {
        if buf[i] == 0 && buf[i + 1] == 0 {
            let nal_start = if buf[i + 2] == 1 {
                i + 3
            } else if buf[i + 2] == 0 && i + 3 < buf.len() && buf[i + 3] == 1 {
                i + 4
            } else {
                i += 1;
                continue;
            };
            if nal_start < buf.len() && (buf[nal_start] & 0x1F) == target {
                return true;
            }
            i = nal_start;
        } else {
            i += 1;
        }
    }
    false
}

pub(crate) fn prepare_timestamp(&mut self, timestamp: u32) {
    if self.timestamp != Some(timestamp) {
        self.timestamp = Some(timestamp);
        self.buffer.clear();
        self.keyframe = false;
        self.in_fu = false;
    }
}

pub(crate) fn append_start_code(&mut self) {
    self.buffer.extend_from_slice(&[0, 0, 0, 1]);
}

pub(crate) fn extend(&mut self, bytes: &[u8]) -> Option<()> {
    if self.buffer.len().saturating_add(bytes.len()) > MAX_VIDEO_FRAME_BYTES {
        self.reset();
        return None;
    }
    self.buffer.extend_from_slice(bytes);
    Some(())
}

pub(crate) fn reset(&mut self) {
    self.timestamp = None;
    self.buffer.clear();
    self.keyframe = false;
    self.in_fu = false;
}

}

pub(crate) fn find_next_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> { let mut index = from; while index + 3 <= data.len() { if data[index..].starts_with(&[0, 0, 1]) { return Some((index, 3)); } if index + 4 <= data.len() && data[index..].starts_with(&[0, 0, 0, 1]) { return Some((index, 4)); } index += 1; } None }

pub(crate) fn h264_annexb_has_idr_slice(frame: &[u8]) -> bool { let mut index = 0usize; while index + 4 <= frame.len() { if frame[index..].starts_with(&[0, 0, 0, 1]) { let nal_start = index + 4; if let Some(byte) = frame.get(nal_start) { let nal_type = byte & 0x1F; if nal_type == 5 { return true; } } index = nal_start; } else if frame[index..].starts_with(&[0, 0, 1]) { let nal_start = index + 3; if let Some(byte) = frame.get(nal_start) { let nal_type = byte & 0x1F; if nal_type == 5 { return true; } } index = nal_start; } else { index += 1; } } false }

pub(crate) fn collect_annexb_nal_types(frame: &[u8]) -> Vec { let mut types = Vec::new(); let mut index = 0usize; while index + 4 <= frame.len() { if frame[index..].starts_with(&[0, 0, 0, 1]) { if let Some(byte) = frame.get(index + 4) { types.push(byte & 0x1F); } index += 4; } else if frame[index..].starts_with(&[0, 0, 1]) { if let Some(byte) = frame.get(index + 3) { types.push(byte & 0x1F); } index += 3; } else { index += 1; } } types }

pub(crate) fn split_h264_annexb_nalus(frame: &[u8]) -> Vec<&[u8]> { let mut nalus = Vec::new(); let mut search_from = 0usize; while let Some((start, start_code_len)) = find_next_start_code(frame, search_from) { let nal_start = start + start_code_len; let nal_end = find_next_start_code(frame, nal_start) .map(|(next_start, _)| next_start) .unwrap_or(frame.len()); if nal_start < nal_end { nalus.push(&frame[nal_start..nal_end]); } search_from = nal_end; } nalus }

pub(crate) fn rewrite_h264_annexb_start_codes( frame: &[u8], first_start_code_len: usize, subsequent_start_code_len: usize, ) -> Option<Vec> { let nalus = split_h264_annexb_nalus(frame); if nalus.is_empty() { return None; }

let extra_start_code_bytes = first_start_code_len
    + subsequent_start_code_len.saturating_mul(nalus.len().saturating_sub(1));
let payload_bytes = nalus.iter().map(|nalu| nalu.len()).sum::<usize>();
let mut out = Vec::with_capacity(extra_start_code_bytes + payload_bytes);

for (index, nalu) in nalus.into_iter().enumerate() {
    let start_code_len = if index == 0 {
        first_start_code_len
    } else {
        subsequent_start_code_len
    };
    match start_code_len {
        3 => out.extend_from_slice(&[0, 0, 1]),
        4 => out.extend_from_slice(&[0, 0, 0, 1]),
        _ => return None,
    }
    out.extend_from_slice(nalu);
}

Some(out)

}

#[cfg(test)] mod tests { use super::*;

#[test]
fn h264_video_depacketizer_resets_on_sequence_gap() {
    let mut depacketizer = H264Depacketizer::default();
    let timestamp = 90_000u32;

    // FU-A start fragment (NAL type 5 IDR via fu_header)
    let start_fragment = [0x7C, 0x85, 0xAA];
    assert_eq!(depacketizer.push(timestamp, false, &start_fragment), None);

    // Simulate sequence gap by calling reset(), as VideoDepacketizerState
    // does when it detects a sequence gap before forwarding to H264Depacketizer.
    depacketizer.reset();

    // FU-A end fragment — should be dropped because the depacketizer was reset
    // and in_fu is now false.
    let end_fragment = [0x7C, 0x45, 0xBB];
    assert_eq!(depacketizer.push(timestamp, true, &end_fragment), None);

    // Next standalone frame should succeed after the gap reset.
    let next_frame = [0x65, 0xCC]; // NAL type 5 (IDR)
    let (frame, keyframe) = depacketizer
        .push(timestamp.wrapping_add(3_000), true, &next_frame)
        .expect("standalone h264 packet should survive after gap reset");

    assert_eq!(frame, vec![0, 0, 0, 1, 0x65, 0xCC]);
    assert!(keyframe);
}

#[test]
fn h264_video_depacketizer_does_not_mark_parameter_sets_only_access_unit_as_keyframe() {
    let mut depacketizer = H264Depacketizer::default();
    let timestamp = 120_000u32;

    // SPS (NAL type 7)
    assert_eq!(
        depacketizer.push(timestamp, false, &[0x67, 0x4D, 0x00, 0x33, 0xAB, 0x40]),
        None
    );
    // PPS (NAL type 8)
    assert_eq!(
        depacketizer.push(timestamp, false, &[0x68, 0xEE, 0x3C, 0x80]),
        None
    );
    // SEI (NAL type 6) with marker — completes the access unit
    let (frame, keyframe) = depacketizer
        .push(timestamp, true, &[0x06, 0x05])
        .expect("parameter-set access unit should still emit a frame");

    assert_eq!(
        frame,
        vec![
            0, 0, 0, 1, 0x67, 0x4D, 0x00, 0x33, 0xAB, 0x40, 0, 0, 0, 1, 0x68, 0xEE, 0x3C, 0x80,
            0, 0, 0, 1, 0x06, 0x05
        ]
    );
    assert!(!keyframe);
    assert!(!h264_annexb_has_idr_slice(&frame));
}

#[test]
fn rewrite_h264_annexb_start_codes_supports_short_variants() {
    let frame = vec![
        0, 0, 0, 1, 0x67, 0x11, 0x22, 0, 0, 0, 1, 0x68, 0x33, 0, 0, 0, 1, 0x65, 0x44, 0x55,
    ];

    let hybrid =
        rewrite_h264_annexb_start_codes(&frame, 4, 3).expect("hybrid rewrite should succeed");
    let all_short = rewrite_h264_annexb_start_codes(&frame, 3, 3)
        .expect("all-short rewrite should succeed");

    assert_eq!(
        hybrid,
        vec![
            0, 0, 0, 1, 0x67, 0x11, 0x22, 0, 0, 1, 0x68, 0x33, 0, 0, 1, 0x65, 0x44, 0x55,
        ]
    );
    assert_eq!(
        all_short,
        vec![
            0, 0, 1, 0x67, 0x11, 0x22, 0, 0, 1, 0x68, 0x33, 0, 0, 1, 0x65, 0x44, 0x55
        ]
    );
}

}