use crate::bit_reader::{BitReader, rbsp_from_ebsp};
use crate::error::CodecError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum HevcNalType {
TrailN = 0,
TrailR = 1,
IdrWRadl = 19,
IdrNLp = 20,
CraNut = 21,
Vps = 32,
Sps = 33,
Pps = 34,
AudNut = 35,
EosNut = 36,
EobNut = 37,
FdNut = 38,
PrefixSeiNut = 39,
SuffixSeiNut = 40,
Other(u8),
}
impl HevcNalType {
pub fn from_u8(v: u8) -> Self {
match v {
0 => Self::TrailN,
1 => Self::TrailR,
19 => Self::IdrWRadl,
20 => Self::IdrNLp,
21 => Self::CraNut,
32 => Self::Vps,
33 => Self::Sps,
34 => Self::Pps,
35 => Self::AudNut,
36 => Self::EosNut,
37 => Self::EobNut,
38 => Self::FdNut,
39 => Self::PrefixSeiNut,
40 => Self::SuffixSeiNut,
other => Self::Other(other),
}
}
pub fn is_keyframe(self) -> bool {
matches!(self, Self::IdrWRadl | Self::IdrNLp | Self::CraNut)
}
}
pub fn parse_nal_header(bytes: &[u8]) -> Result<HevcNalType, CodecError> {
if bytes.len() < 2 {
return Err(CodecError::EndOfStream {
needed: 2,
remaining: bytes.len(),
});
}
let nal_type = (bytes[0] >> 1) & 0x3F;
Ok(HevcNalType::from_u8(nal_type))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HevcSps {
pub general_profile_space: u8,
pub general_tier_flag: bool,
pub general_profile_idc: u8,
pub general_profile_compatibility_flags: u32,
pub general_level_idc: u8,
pub chroma_format_idc: u32,
pub pic_width_in_luma_samples: u32,
pub pic_height_in_luma_samples: u32,
}
impl HevcSps {
pub fn codec_string(&self) -> String {
format!(
"hev1.{}{}.{:X}.L{}.B0",
match self.general_profile_space {
0 => String::new(),
1 => "A".to_string(),
2 => "B".to_string(),
3 => "C".to_string(),
_ => String::new(),
},
self.general_profile_idc,
self.general_profile_compatibility_flags,
self.general_level_idc,
)
}
}
pub fn parse_sps(payload: &[u8]) -> Result<HevcSps, CodecError> {
let rbsp = rbsp_from_ebsp(payload);
let mut r = BitReader::new(&rbsp);
let _vps_id = r.read_bits(4)?;
let max_sub_layers_minus1 = r.read_bits(3)? as u8;
if max_sub_layers_minus1 > 6 {
return Err(CodecError::MalformedSps("sps_max_sub_layers_minus1 > 6"));
}
let _tid_nesting = r.read_bit()?;
let sps = parse_ptl_general(&mut r)?;
parse_ptl_sublayers(&mut r, max_sub_layers_minus1)?;
let _sps_id = r.read_ue_v()?;
let chroma_format_idc = r.read_ue_v()?;
if chroma_format_idc > 3 {
return Err(CodecError::MalformedSps("chroma_format_idc > 3"));
}
if chroma_format_idc == 3 {
let _ = r.read_bit()?;
}
let pic_width_in_luma_samples = r.read_ue_v()?;
let pic_height_in_luma_samples = r.read_ue_v()?;
if pic_width_in_luma_samples == 0
|| pic_height_in_luma_samples == 0
|| pic_width_in_luma_samples > 16384
|| pic_height_in_luma_samples > 16384
{
return Err(CodecError::MalformedSps("implausible SPS dimensions"));
}
Ok(HevcSps {
general_profile_space: sps.general_profile_space,
general_tier_flag: sps.general_tier_flag,
general_profile_idc: sps.general_profile_idc,
general_profile_compatibility_flags: sps.general_profile_compatibility_flags,
general_level_idc: sps.general_level_idc,
chroma_format_idc,
pic_width_in_luma_samples,
pic_height_in_luma_samples,
})
}
#[derive(Debug, Clone)]
struct PtlGeneral {
general_profile_space: u8,
general_tier_flag: bool,
general_profile_idc: u8,
general_profile_compatibility_flags: u32,
general_level_idc: u8,
}
fn parse_ptl_general(r: &mut BitReader<'_>) -> Result<PtlGeneral, CodecError> {
let general_profile_space = r.read_bits(2)? as u8;
let general_tier_flag = r.read_bit()? == 1;
let general_profile_idc = r.read_bits(5)? as u8;
let general_profile_compatibility_flags = r.read_bits(32)?;
let _progressive = r.read_bit()?;
let _interlaced = r.read_bit()?;
let _non_packed = r.read_bit()?;
let _frame_only = r.read_bit()?;
r.skip_bits(44)?;
let general_level_idc = r.read_bits(8)? as u8;
Ok(PtlGeneral {
general_profile_space,
general_tier_flag,
general_profile_idc,
general_profile_compatibility_flags,
general_level_idc,
})
}
fn parse_ptl_sublayers(r: &mut BitReader<'_>, max_sub_layers_minus1: u8) -> Result<(), CodecError> {
if max_sub_layers_minus1 == 0 {
return Ok(());
}
let n = max_sub_layers_minus1 as usize;
let mut profile_present = [false; 6];
let mut level_present = [false; 6];
for i in 0..n {
profile_present[i] = r.read_bit()? == 1;
level_present[i] = r.read_bit()? == 1;
}
let padding_bits = 2 * (8 - n);
r.skip_bits(padding_bits)?;
for i in 0..n {
if profile_present[i] {
r.skip_bits(88)?;
}
if level_present[i] {
r.skip_bits(8)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
struct BitWriter {
bytes: Vec<u8>,
bit_pos: usize,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 0,
}
}
fn write_bit(&mut self, b: u32) {
if self.bit_pos % 8 == 0 {
self.bytes.push(0);
}
let byte_idx = self.bit_pos / 8;
let shift = 7 - (self.bit_pos % 8);
self.bytes[byte_idx] |= ((b & 1) as u8) << shift;
self.bit_pos += 1;
}
fn write_bits(&mut self, value: u32, n: u8) {
for i in (0..n).rev() {
self.write_bit((value >> i) & 1);
}
}
fn write_ue(&mut self, value: u32) {
let code = (value as u64) + 1;
let mut k = 0u8;
while (1u64 << (k + 1)) <= code {
k += 1;
}
for _ in 0..k {
self.write_bit(0);
}
self.write_bits(code as u32, k + 1);
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
fn build_synthetic_sps(max_sub_layers_minus1: u8) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(0, 4);
w.write_bits(max_sub_layers_minus1 as u32, 3);
w.write_bit(1);
w.write_bits(0, 2); w.write_bit(0); w.write_bits(1, 5); w.write_bits(0x60000000, 32); w.write_bit(0);
w.write_bit(0);
w.write_bit(0);
w.write_bit(0);
for _ in 0..44 {
w.write_bit(0);
}
w.write_bits(93, 8); for _ in 0..max_sub_layers_minus1 {
w.write_bit(1); w.write_bit(1); }
if max_sub_layers_minus1 > 0 {
let padding = 2 * (8 - max_sub_layers_minus1 as usize);
for _ in 0..padding {
w.write_bit(0);
}
}
for _ in 0..max_sub_layers_minus1 {
for _ in 0..88 {
w.write_bit(0);
}
w.write_bits(60, 8); }
w.write_ue(0);
w.write_ue(1);
w.write_ue(1920);
w.write_ue(1080);
w.into_bytes()
}
#[test]
fn nal_type_round_trip() {
assert_eq!(HevcNalType::from_u8(33), HevcNalType::Sps);
assert_eq!(HevcNalType::from_u8(19), HevcNalType::IdrWRadl);
assert!(HevcNalType::from_u8(19).is_keyframe());
assert!(HevcNalType::from_u8(21).is_keyframe());
assert!(!HevcNalType::from_u8(1).is_keyframe());
assert_eq!(HevcNalType::from_u8(63), HevcNalType::Other(63));
}
#[test]
fn nal_header_rejects_short_input() {
assert!(matches!(parse_nal_header(&[0x42]), Err(CodecError::EndOfStream { .. })));
}
#[test]
fn nal_header_decodes_sps_byte() {
assert_eq!(parse_nal_header(&[0x42, 0x01]).unwrap(), HevcNalType::Sps);
}
#[test]
fn parse_sps_rejects_empty_payload() {
assert!(parse_sps(&[]).is_err());
}
#[test]
fn parse_sps_rejects_all_zeros() {
let zeros = vec![0u8; 64];
assert!(parse_sps(&zeros).is_err());
}
#[test]
fn parse_sps_decodes_synthetic_single_sublayer() {
let sps_bytes = build_synthetic_sps(0);
let sps = parse_sps(&sps_bytes).expect("single-sublayer SPS should parse");
assert_eq!(sps.general_profile_idc, 1);
assert_eq!(sps.general_profile_compatibility_flags, 0x60000000);
assert_eq!(sps.general_level_idc, 93);
assert_eq!(sps.chroma_format_idc, 1);
assert_eq!(sps.pic_width_in_luma_samples, 1920);
assert_eq!(sps.pic_height_in_luma_samples, 1080);
assert_eq!(sps.codec_string(), "hev1.1.60000000.L93.B0");
}
#[test]
fn parse_sps_decodes_two_sublayer_stream() {
let sps_bytes = build_synthetic_sps(1);
let sps = parse_sps(&sps_bytes).expect("two-sublayer SPS should parse");
assert_eq!(sps.general_profile_idc, 1);
assert_eq!(sps.general_level_idc, 93);
assert_eq!(sps.pic_width_in_luma_samples, 1920);
assert_eq!(sps.pic_height_in_luma_samples, 1080);
}
#[test]
fn parse_sps_decodes_max_sublayer_stream() {
let sps_bytes = build_synthetic_sps(6);
let sps = parse_sps(&sps_bytes).expect("max-sublayer SPS should parse");
assert_eq!(sps.pic_width_in_luma_samples, 1920);
assert_eq!(sps.pic_height_in_luma_samples, 1080);
}
#[test]
fn parse_sps_decodes_real_x265_single_sublayer() {
let hex = "0101600000030090000003000003003ca00a080f165ba4a4c2f0168080000003008000000f0400";
let sps_bytes: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect();
let sps = parse_sps(&sps_bytes).expect("real x265 SPS should parse");
assert_eq!(sps.general_profile_idc, 1); assert_eq!(sps.general_level_idc, 60); assert_eq!(sps.chroma_format_idc, 1); assert_eq!(sps.pic_width_in_luma_samples, 320);
assert_eq!(sps.pic_height_in_luma_samples, 240);
assert_eq!(sps.codec_string(), "hev1.1.60000000.L60.B0");
}
#[test]
fn codec_string_format() {
let sps = HevcSps {
general_profile_space: 0,
general_tier_flag: false,
general_profile_idc: 1,
general_profile_compatibility_flags: 0x60000000,
general_level_idc: 93,
chroma_format_idc: 1,
pic_width_in_luma_samples: 1920,
pic_height_in_luma_samples: 1080,
};
assert_eq!(sps.codec_string(), "hev1.1.60000000.L93.B0");
}
}