use super::bitreader::BitReader;
use super::nal::{conformance_dims, iter_nals, unescape_rbsp};
use super::parser::{CodecParser, VideoParams};
use crate::CodecId;
pub const NAL_VPS: u8 = 32;
pub const NAL_SPS: u8 = 33;
pub const NAL_PPS: u8 = 34;
fn nal_type(nal: &[u8]) -> Option<u8> {
nal.first().map(|b| (b >> 1) & 0x3F)
}
fn is_irap(t: u8) -> bool {
(16..=23).contains(&t)
}
fn read_profile_tier_level(r: &mut BitReader, max_sub_layers_minus1: u32) -> Option<(u8, u8, u8)> {
let _general_profile_space = r.read_bits(2)?;
let general_tier_flag = r.read_bit()? as u8;
let general_profile_idc = r.read_bits(5)? as u8;
let _compat = r.read_bits(32)?; let _constraint_hi = r.read_bits(32)?; let _constraint_lo = r.read_bits(16)?; let general_level_idc = r.read_bits(8)? as u8;
let mut sub_profile = [false; 8];
let mut sub_level = [false; 8];
for i in 0..max_sub_layers_minus1 as usize {
sub_profile[i] = r.read_bit()? == 1;
sub_level[i] = r.read_bit()? == 1;
}
if max_sub_layers_minus1 > 0 {
for _ in max_sub_layers_minus1..8 {
let _reserved = r.read_bits(2)?;
}
}
for i in 0..max_sub_layers_minus1 as usize {
if sub_profile[i] {
r.read_bits(32)?;
r.read_bits(32)?;
r.read_bits(24)?;
}
if sub_level[i] {
r.read_bits(8)?;
}
}
Some((general_profile_idc, general_tier_flag, general_level_idc))
}
fn parse_sps(nal: &[u8]) -> Option<VideoParams> {
if nal.len() < 3 || nal_type(nal)? != NAL_SPS {
return None;
}
let rbsp = unescape_rbsp(&nal[2..]);
let mut r = BitReader::new(&rbsp);
let _sps_video_parameter_set_id = r.read_bits(4)?;
let max_sub_layers_minus1 = r.read_bits(3)?;
let _sps_temporal_id_nesting = r.read_bit()?;
let (profile, tier, level) = read_profile_tier_level(&mut r, max_sub_layers_minus1)?;
let _sps_seq_parameter_set_id = r.read_ue()?;
let chroma_format_idc = r.read_ue()?;
if chroma_format_idc == 3 {
let _separate_colour_plane = r.read_bit()?;
}
let width_luma = r.read_ue()?;
let height_luma = r.read_ue()?;
let (mut l, mut rr, mut t, mut b) = (0, 0, 0, 0);
if r.read_bit()? == 1 {
l = r.read_ue()?;
rr = r.read_ue()?;
t = r.read_ue()?;
b = r.read_ue()?;
}
let (width, height) = conformance_dims(width_luma, height_luma, chroma_format_idc, l, rr, t, b);
Some(VideoParams {
width,
height,
profile,
level,
tier,
bit_depth: 8,
})
}
pub struct H265;
impl CodecParser for H265 {
const CODEC: CodecId = CodecId::H265;
fn parse_config(data: &[u8]) -> Option<VideoParams> {
iter_nals(data).find_map(parse_sps)
}
fn is_random_access_point(data: &[u8]) -> bool {
iter_nals(data).any(|n| nal_type(n).is_some_and(|t| t <= 31 && is_irap(t)))
}
fn carries_config(data: &[u8]) -> bool {
iter_nals(data)
.any(|n| nal_type(n).is_some_and(|t| matches!(t, NAL_VPS | NAL_SPS | NAL_PPS)))
}
fn hls_codec_string(p: &VideoParams) -> String {
let tier = if p.tier == 1 { 'H' } else { 'L' };
format!("hvc1.{}.6.{}{}.B0", p.profile, tier, p.level)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::testutil::BitWriter;
fn sps_1920x1080() -> Vec<u8> {
let mut w = BitWriter::default();
w.bits(0, 4); w.bits(0, 3); w.bit(0); w.bits(0, 2); w.bit(0); w.bits(1, 5); w.bits(0, 32); w.bits(0, 32); w.bits(0, 16); w.bits(120, 8); w.ue(0); w.ue(1); w.ue(1920); w.ue(1088); w.bit(1); w.ue(0); w.ue(0); w.ue(0); w.ue(2); let mut nal = vec![0x42u8, 0x01]; nal.extend_from_slice(&w.bytes());
nal
}
#[test]
fn parse_sps_extracts_resolution() {
let p = parse_sps(&sps_1920x1080()).expect("parse");
assert_eq!((p.width, p.height), (1920, 1084));
assert_eq!((p.profile, p.tier, p.level), (1, 0, 120));
}
#[test]
fn classifies_irap_and_config() {
let mut au = vec![0, 0, 0, 1];
au.extend_from_slice(&sps_1920x1080());
au.extend_from_slice(&[0, 0, 0, 1, 0x26, 0x01, 0xAA]);
assert!(H265::is_random_access_point(&au));
assert!(H265::carries_config(&au));
let p = H265::parse_config(&au).expect("params");
assert_eq!(p.width, 1920);
assert_eq!(H265::hls_codec_string(&p), "hvc1.1.6.L120.B0");
assert!(!H265::is_random_access_point(&[
0, 0, 0, 1, 0x02, 0x01, 0xAA
]));
}
}