use super::h264::{find_start_code, skip_start_code};
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
const NAL_VPS: u8 = 32;
const NAL_SPS: u8 = 33;
const NAL_PPS: u8 = 34;
const NAL_AUD: u8 = 35;
const _NAL_UNSPEC62_DV_RPU: u8 = 62;
const NAL_BLA_W_LP: u8 = 16;
const NAL_RSV_IRAP_VCL23: u8 = 23;
pub struct HevcParser {
vps: Option<Vec<u8>>,
sps: Option<Vec<u8>>,
pps: Option<Vec<u8>>,
}
impl Default for HevcParser {
fn default() -> Self {
Self::new()
}
}
impl HevcParser {
pub fn new() -> Self {
Self {
vps: None,
sps: None,
pps: None,
}
}
}
impl CodecParser for HevcParser {
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 data = &pes.data;
let mut keyframe = false;
let mut pos = 0;
while let Some(sc_pos) = find_start_code(data, pos) {
if let Some(nal_start) = skip_start_code(data, sc_pos) {
let next = find_start_code(data, nal_start).unwrap_or(data.len());
let mut end = next;
while end > nal_start && data[end - 1] == 0x00 {
end -= 1;
}
if nal_start < data.len() {
let nal_type = (data[nal_start] >> 1) & 0x3F;
match nal_type {
NAL_VPS => self.vps = Some(data[nal_start..end].to_vec()),
NAL_SPS => self.sps = Some(data[nal_start..end].to_vec()),
NAL_PPS => self.pps = Some(data[nal_start..end].to_vec()),
t if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&t) => {
keyframe = true;
}
_ => {}
}
}
pos = next;
} else {
break;
}
}
let mut frame_data = Vec::new();
let mut pos = 0;
while let Some(sc_pos) = find_start_code(&pes.data, pos) {
if let Some(nal_start) = skip_start_code(&pes.data, sc_pos) {
let next = find_start_code(&pes.data, nal_start).unwrap_or(pes.data.len());
let mut end = next;
while end > nal_start && pes.data[end - 1] == 0x00 {
end -= 1;
}
if nal_start < pes.data.len() {
let nal_type = (pes.data[nal_start] >> 1) & 0x3F;
if nal_type != NAL_VPS
&& nal_type != NAL_SPS
&& nal_type != NAL_PPS
&& nal_type != NAL_AUD
{
let nal = &pes.data[nal_start..end];
let len = nal.len() as u32;
frame_data.extend_from_slice(&len.to_be_bytes());
frame_data.extend_from_slice(nal);
}
}
pos = next;
} else {
break;
}
}
if frame_data.is_empty() {
return Vec::new();
}
vec![Frame {
pts_ns,
keyframe,
data: frame_data,
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
let vps = self.vps.as_ref()?;
let sps = self.sps.as_ref()?;
let pps = self.pps.as_ref()?;
let mut record = Vec::new();
record.push(1); if sps.len() > 3 {
record.push(sps[1]); } else {
record.push(0);
}
if sps.len() > 5 {
record.extend_from_slice(&sps[2..6]);
} else {
record.extend_from_slice(&[0, 0, 0, 0]);
}
if sps.len() > 11 {
record.extend_from_slice(&sps[6..12]);
} else {
let avail = sps.len().saturating_sub(6).min(6);
if avail > 0 {
record.extend_from_slice(&sps[6..6 + avail]);
record.extend_from_slice(&vec![0u8; 6 - avail]);
} else {
record.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
}
}
record.push(if sps.len() > 12 { sps[12] } else { 0 });
record.extend_from_slice(&[0xF0, 0x00]);
record.push(0xFC);
record.push(0xFC | 1); record.push(0xF8);
record.push(0xF8);
record.extend_from_slice(&[0, 0]);
record.push(0x03); record.push(3);
record.push(0x20 | (NAL_VPS & 0x3F)); record.extend_from_slice(&[0, 1]); record.push((vps.len() >> 8) as u8);
record.push(vps.len() as u8);
record.extend_from_slice(vps);
record.push(0x20 | (NAL_SPS & 0x3F));
record.extend_from_slice(&[0, 1]);
record.push((sps.len() >> 8) as u8);
record.push(sps.len() as u8);
record.extend_from_slice(sps);
record.push(0x20 | (NAL_PPS & 0x3F));
record.extend_from_slice(&[0, 1]);
record.push((pps.len() >> 8) as u8);
record.push(pps.len() as u8);
record.extend_from_slice(pps);
Some(record)
}
}
#[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,
}
}
fn hevc_nal_header(nal_type: u8) -> [u8; 2] {
[(nal_type & 0x3F) << 1, 0x01] }
#[test]
fn parse_vps_sps_pps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let vps_hdr = hevc_nal_header(32);
data.extend_from_slice(&vps_hdr);
data.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let sps_hdr = hevc_nal_header(33);
data.extend_from_slice(&sps_hdr);
data.extend_from_slice(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let pps_hdr = hevc_nal_header(34);
data.extend_from_slice(&pps_hdr);
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20, 0x30]);
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 VPS+SPS+PPS"
);
let cp = cp.unwrap();
assert_eq!(cp[0], 1);
assert_eq!(cp[22], 3);
assert!(
cp.len() > 23,
"codec_private should contain VPS+SPS+PPS data"
);
}
#[test]
fn codec_private_none_before_params() {
let parser = HevcParser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn codec_private_none_missing_pps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1)); data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
parser.parse(&pes);
assert!(
parser.codec_private().is_none(),
"should be None without PPS"
);
}
#[test]
fn parse_irap_keyframe_idr_w_radl() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(19));
data.extend_from_slice(&[0x10, 0x20, 0x30]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
frames[0].keyframe,
"IDR_W_RADL (type 19) should be keyframe"
);
}
#[test]
fn parse_irap_keyframe_bla() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(16));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "BLA_W_LP (type 16) should be keyframe");
}
#[test]
fn parse_irap_keyframe_cra() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(21));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "CRA (type 21) should be keyframe");
}
#[test]
fn parse_irap_type_23() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(23));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "type 23 should be keyframe");
}
#[test]
fn parse_trailing_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1));
data.extend_from_slice(&[0x10, 0x20, 0x30]);
let pes = make_pes(data, Some(180000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
!frames[0].keyframe,
"TRAIL_R (type 1) should not be keyframe"
);
}
#[test]
fn parse_tsa_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(2));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "TSA_N (type 2) should not be keyframe");
}
#[test]
fn param_sets_stripped_from_frame() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20]);
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 as usize + 4,
fd.len(),
"frame should contain exactly one length-prefixed NAL"
);
}
#[test]
fn parse_empty_pes() {
let mut parser = HevcParser::new();
let pes = make_pes(Vec::new(), Some(0));
let frames = parser.parse(&pes);
assert!(frames.is_empty());
}
#[test]
fn pts_conversion() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1));
data.extend_from_slice(&[0x10, 0x20]);
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 dv_rpu_nal_preserved() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20, 0x30]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let rpu_hdr = hevc_nal_header(62);
data.extend_from_slice(&rpu_hdr);
let rpu_payload = [0xF0, 0xF1, 0xF2, 0xF3, 0xF4];
data.extend_from_slice(&rpu_payload);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1, "should produce one frame");
assert!(frames[0].keyframe, "IDR should mark keyframe");
let fd = &frames[0].data;
let mut nal_types = Vec::new();
let mut offset = 0;
while offset + 4 <= fd.len() {
let length =
u32::from_be_bytes([fd[offset], fd[offset + 1], fd[offset + 2], fd[offset + 3]])
as usize;
offset += 4;
assert!(offset + length <= fd.len(), "NAL length exceeds frame data");
let nal_type = (fd[offset] >> 1) & 0x3F;
nal_types.push(nal_type);
offset += length;
}
assert!(
nal_types.contains(&19),
"frame data must contain IDR NAL (type 19), got: {:?}",
nal_types
);
assert!(
nal_types.contains(&62),
"frame data must contain Dolby Vision RPU NAL (type 62), got: {:?}",
nal_types
);
assert_eq!(
nal_types.len(),
2,
"frame data should have exactly 2 NALs (IDR + RPU), got: {:?}",
nal_types
);
let mut offset = 0;
while offset + 4 <= fd.len() {
let length =
u32::from_be_bytes([fd[offset], fd[offset + 1], fd[offset + 2], fd[offset + 3]])
as usize;
offset += 4;
let nal_type = (fd[offset] >> 1) & 0x3F;
if nal_type == 62 {
let nal_payload = &fd[offset + 2..offset + length];
assert_eq!(
nal_payload, &rpu_payload,
"RPU payload must be preserved verbatim"
);
}
offset += length;
}
}
}