use super::{pts_to_ns, CodecParser, Frame, PesPacket};
const NAL_SLICE_IDR: u8 = 5;
const NAL_SPS: u8 = 7;
const NAL_PPS: u8 = 8;
const NAL_AUD: u8 = 9;
pub struct H264Parser {
sps: Option<Vec<u8>>,
pps: Option<Vec<u8>>,
}
impl Default for H264Parser {
fn default() -> Self {
Self::new()
}
}
impl H264Parser {
pub fn new() -> Self {
Self {
sps: None,
pps: None,
}
}
}
impl CodecParser for H264Parser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
if pes.data.is_empty() {
return Vec::new();
}
let pts_ns = pes.dts.or(pes.pts).map(pts_to_ns).unwrap_or(0);
let mut keyframe = false;
let mut frame_data = Vec::new();
for nal in NalIterator::new(&pes.data) {
let nal_type = nal[0] & 0x1F;
match nal_type {
NAL_SPS => {
self.sps = Some(nal.to_vec());
}
NAL_PPS => {
self.pps = Some(nal.to_vec());
}
NAL_SLICE_IDR => {
keyframe = true;
}
_ => {}
}
}
for nal in NalIterator::new(&pes.data) {
let nal_type = nal[0] & 0x1F;
if nal_type == NAL_SPS || nal_type == NAL_PPS || nal_type == NAL_AUD {
continue;
}
let len = nal.len() as u32;
frame_data.extend_from_slice(&len.to_be_bytes());
frame_data.extend_from_slice(nal);
}
if frame_data.is_empty() {
return Vec::new();
}
vec![Frame {
pts_ns,
keyframe,
data: frame_data,
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
let sps = self.sps.as_ref()?;
let pps = self.pps.as_ref()?;
if sps.len() < 4 {
return None;
}
let mut record = vec![
1, sps[1], sps[2], sps[3], 0xFF, 0xE1, (sps.len() >> 8) as u8,
sps.len() as u8,
];
record.extend_from_slice(sps);
record.push(1); record.push((pps.len() >> 8) as u8);
record.push(pps.len() as u8);
record.extend_from_slice(pps);
Some(record)
}
}
struct NalIterator<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> NalIterator<'a> {
fn new(data: &'a [u8]) -> Self {
let pos = find_start_code(data, 0).unwrap_or(data.len());
Self { data, pos }
}
}
impl<'a> Iterator for NalIterator<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if self.pos >= self.data.len() {
return None;
}
let nal_start = skip_start_code(self.data, self.pos)?;
let nal_end = find_start_code(self.data, nal_start).unwrap_or(self.data.len());
let mut end = nal_end;
while end > nal_start && self.data[end - 1] == 0x00 {
end -= 1;
}
self.pos = nal_end;
if end > nal_start {
Some(&self.data[nal_start..end])
} else {
self.next()
}
}
}
pub fn find_start_code(data: &[u8], from: usize) -> Option<usize> {
if data.len() < from + 3 {
return None;
}
(from..data.len() - 2).find(|&i| data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01)
}
pub fn skip_start_code(data: &[u8], pos: usize) -> Option<usize> {
if pos + 2 >= data.len() {
return None;
}
if data[pos] == 0x00 && data[pos + 1] == 0x00 {
if pos + 3 < data.len() && data[pos + 2] == 0x00 && data[pos + 3] == 0x01 {
return Some(pos + 4); }
if data[pos + 2] == 0x01 {
return Some(pos + 3); }
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::ts::PesPacket;
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
pid: 0x1011,
pts,
dts: None,
data,
}
}
#[test]
fn find_start_code_3byte() {
let data = [0x00, 0x00, 0x01, 0x65];
assert_eq!(find_start_code(&data, 0), Some(0));
}
#[test]
fn find_start_code_4byte() {
let data = [0x00, 0x00, 0x00, 0x01, 0x65];
assert_eq!(find_start_code(&data, 0), Some(1));
}
#[test]
fn find_start_code_offset() {
let data = [0xFF, 0xFF, 0x00, 0x00, 0x01, 0x09];
assert_eq!(find_start_code(&data, 0), Some(2));
}
#[test]
fn find_start_code_none() {
let data = [0x00, 0x00, 0x00, 0x00];
assert_eq!(find_start_code(&data, 0), None);
}
#[test]
fn find_start_code_too_short() {
let data = [0x00, 0x00];
assert_eq!(find_start_code(&data, 0), None);
}
#[test]
fn parse_sps_pps() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x67); data.extend_from_slice(&[0x42, 0x00, 0x1E, 0xAB, 0xCD]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x68); data.extend_from_slice(&[0xCE, 0x01]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65); data.extend_from_slice(&[0x88, 0x00, 0x10]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
let cp = parser.codec_private();
assert!(
cp.is_some(),
"codec_private should be Some after seeing SPS+PPS"
);
let cp = cp.unwrap();
assert_eq!(cp[0], 1, "configurationVersion");
assert_eq!(cp[1], 0x42, "profile from SPS[1]");
assert_eq!(cp[2], 0x00, "compatibility from SPS[2]");
assert_eq!(cp[3], 0x1E, "level from SPS[3]");
assert_eq!(cp[4], 0xFF, "reserved + lengthSizeMinusOne=3");
assert_eq!(cp[5], 0xE1, "reserved + numSPS=1");
assert_eq!(frames.len(), 1);
}
#[test]
fn codec_private_none_before_sps_pps() {
let parser = H264Parser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn parse_idr_keyframe() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65); data.extend_from_slice(&[0x88, 0x00, 0x10, 0x20]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
frames[0].keyframe,
"IDR slice should be detected as keyframe"
);
}
#[test]
fn parse_non_idr() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41); data.extend_from_slice(&[0x9A, 0x00, 0x10]);
let pes = make_pes(data, Some(180000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "non-IDR slice should not be keyframe");
}
#[test]
fn length_prefix_conversion() {
let mut parser = H264Parser::new();
let nal_payload = [0x41, 0xAA, 0xBB, 0xCC, 0xDD]; let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&nal_payload);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
let frame_data = &frames[0].data;
assert!(
frame_data.len() >= 4,
"frame data should have length prefix"
);
let length =
u32::from_be_bytes([frame_data[0], frame_data[1], frame_data[2], frame_data[3]]);
assert_eq!(
length as usize,
nal_payload.len(),
"length prefix should match NAL size"
);
assert_eq!(&frame_data[4..], &nal_payload);
for i in 0..frame_data.len().saturating_sub(2) {
let is_sc =
frame_data[i] == 0x00 && frame_data[i + 1] == 0x00 && frame_data[i + 2] == 0x01;
assert!(!is_sc, "output should not contain Annex B start codes");
}
}
#[test]
fn sps_pps_aud_stripped_from_frame_data() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x09);
data.push(0xF0);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x67);
data.extend_from_slice(&[0x42, 0x00, 0x1E, 0xAB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x68);
data.extend_from_slice(&[0xCE, 0x01]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65);
data.extend_from_slice(&[0x88, 0x00]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
let fd = &frames[0].data;
let length = u32::from_be_bytes([fd[0], fd[1], fd[2], fd[3]]);
assert_eq!(length, 2);
assert_eq!(fd[4], 0x65); }
#[test]
fn pts_conversion() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41);
data.extend_from_slice(&[0x00, 0x10]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn parse_empty_pes() {
let mut parser = H264Parser::new();
let pes = make_pes(Vec::new(), Some(0));
let frames = parser.parse(&pes);
assert!(frames.is_empty());
}
#[test]
fn dts_preferred_over_pts() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41);
data.extend_from_slice(&[0x00, 0x10]);
let pes = PesPacket {
pid: 0x1011,
pts: Some(180000), dts: Some(90000), data,
};
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
}