use crate::error::{Error, Result};
use crate::init_segment::bounded_entry_count;
use crate::nalu_types::{HevcNalArray, HevcNalUnit, VALID_LENGTH_SIZES};
use crate::wire_cursor::{read_u8, read_u16, read_u32, read_u48};
use alloc::vec::Vec;
use broadcast_common::error::InvalidValue;
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HEVCDecoderConfigurationRecord {
pub configuration_version: u8,
pub general_profile_space: u8,
pub general_tier_flag: bool,
pub general_profile_idc: u8,
pub general_profile_compatibility_flags: u32,
pub general_constraint_indicator_flags: u64,
pub general_level_idc: u8,
pub min_spatial_segmentation_idc: u16,
pub parallelism_type: u8,
pub chroma_format_idc: u8,
pub bit_depth_luma_minus8: u8,
pub bit_depth_chroma_minus8: u8,
pub avg_frame_rate: u16,
pub constant_frame_rate: u8,
pub num_temporal_layers: u8,
pub temporal_id_nested: bool,
pub length_size_minus_one: u8,
pub arrays: Vec<HevcNalArray>,
}
impl HEVCDecoderConfigurationRecord {
pub fn is_valid_length_size(v: u8) -> bool {
(v == 0) | (v == 1) | (v == 3)
}
pub fn rfc6381(&self) -> alloc::string::String {
crate::sps::rfc6381_hvc1(&crate::sps::HevcSpsInfo {
general_profile_space: self.general_profile_space,
general_tier_flag: self.general_tier_flag,
general_profile_idc: self.general_profile_idc,
general_profile_compatibility_flags: self.general_profile_compatibility_flags,
general_constraint_indicator_flags: self.general_constraint_indicator_flags,
general_level_idc: self.general_level_idc,
chroma_format_idc: self.chroma_format_idc,
bit_depth_luma: self.bit_depth_luma_minus8 + 8,
bit_depth_chroma: self.bit_depth_chroma_minus8 + 8,
width: 0,
height: 0,
num_units_in_tick: None,
time_scale: None,
fps: None,
})
}
}
const ARRAY_MIN_LEN: usize = 3;
const NALU_MIN_LEN: usize = 2;
impl<'a> Parse<'a> for HEVCDecoderConfigurationRecord {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let mut cursor = 0usize;
let configuration_version = read_u8(bytes, &mut cursor, "HEVC configurationVersion")?;
let b1 = read_u8(bytes, &mut cursor, "general_profile_space/tier/idc")?;
let general_profile_space = (b1 >> 6) & 0x03;
let general_tier_flag = ((b1 >> 5) & 0x01) != 0;
let general_profile_idc = b1 & 0x1F;
let general_profile_compatibility_flags =
read_u32(bytes, &mut cursor, "general_profile_compatibility_flags")?;
let general_constraint_indicator_flags =
read_u48(bytes, &mut cursor, "general_constraint_indicator_flags")?;
let general_level_idc = read_u8(bytes, &mut cursor, "general_level_idc")?;
let msid_bytes = read_u16(bytes, &mut cursor, "min_spatial_segmentation_idc")?;
let _reserved_msid = (msid_bytes >> 12) & 0x0F;
let min_spatial_segmentation_idc = msid_bytes & 0x0FFF;
let b_pt = read_u8(bytes, &mut cursor, "parallelismType")?;
let _reserved_pt = (b_pt >> 2) & 0x3F;
let parallelism_type = b_pt & 0x03;
let b_cf = read_u8(bytes, &mut cursor, "chroma_format_idc")?;
let _reserved_cf = (b_cf >> 2) & 0x3F;
let chroma_format_idc = b_cf & 0x03;
let b_bdl = read_u8(bytes, &mut cursor, "bit_depth_luma_minus8")?;
let _reserved_bdl = (b_bdl >> 3) & 0x1F;
let bit_depth_luma_minus8 = b_bdl & 0x07;
let b_bdc = read_u8(bytes, &mut cursor, "bit_depth_chroma_minus8")?;
let _reserved_bdc = (b_bdc >> 3) & 0x1F;
let bit_depth_chroma_minus8 = b_bdc & 0x07;
let avg_frame_rate = read_u16(bytes, &mut cursor, "avgFrameRate")?;
let b_cfr = read_u8(bytes, &mut cursor, "constantFrameRate/temporal")?;
let constant_frame_rate = (b_cfr >> 6) & 0x03;
let num_temporal_layers = (b_cfr >> 3) & 0x07;
let temporal_id_nested = ((b_cfr >> 2) & 0x01) != 0;
let length_size_minus_one = b_cfr & 0x03;
if !VALID_LENGTH_SIZES.contains(&length_size_minus_one) {
return Err(Error::InvalidValue(InvalidValue {
field: "lengthSizeMinusOne",
value: length_size_minus_one as u64,
reason: "must be 0, 1, or 3 (value 2 is invalid per ISO/IEC 14496-15:2017 §8.3.3)",
}));
}
let num_arrays = read_u8(bytes, &mut cursor, "numOfArrays")? as usize;
let mut arrays = Vec::with_capacity(bounded_entry_count(
bytes.len().saturating_sub(cursor),
ARRAY_MIN_LEN,
num_arrays,
));
for _ in 0..num_arrays {
let b_arr = read_u8(bytes, &mut cursor, "NAL array header")?;
let array_completeness = (b_arr >> 7) != 0;
let _reserved_arr = (b_arr >> 6) & 0x01;
let nal_unit_type = b_arr & 0x3F;
let num_nalus = read_u16(bytes, &mut cursor, "numNalus")? as usize;
let mut nalus = Vec::with_capacity(bounded_entry_count(
bytes.len().saturating_sub(cursor),
NALU_MIN_LEN,
num_nalus,
));
for _ in 0..num_nalus {
let nal_unit_len = read_u16(bytes, &mut cursor, "nalUnitLength")? as usize;
need_in(bytes, cursor + nal_unit_len, "HEVC nalUnit")?;
let nalu_data = bytes[cursor..cursor + nal_unit_len].to_vec();
cursor += nal_unit_len;
nalus.push(HevcNalUnit(nalu_data));
}
arrays.push(HevcNalArray {
array_completeness,
nal_unit_type,
nalus,
});
}
Ok(Self {
configuration_version,
general_profile_space,
general_tier_flag,
general_profile_idc,
general_profile_compatibility_flags,
general_constraint_indicator_flags,
general_level_idc,
min_spatial_segmentation_idc,
parallelism_type,
chroma_format_idc,
bit_depth_luma_minus8,
bit_depth_chroma_minus8,
avg_frame_rate,
constant_frame_rate,
num_temporal_layers,
temporal_id_nested,
length_size_minus_one,
arrays,
})
}
}
impl Serialize for HEVCDecoderConfigurationRecord {
type Error = Error;
fn serialized_len(&self) -> usize {
1 + 1
+ 4
+ 6
+ 1
+ 2
+ 1
+ 1
+ 1
+ 1
+ 2
+ 1
+ 1
+ self
.arrays
.iter()
.map(|a| {
3 + a.nalus.iter().map(|n| 2 + n.0.len()).sum::<usize>()
})
.sum::<usize>()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
need_out(buf, need)?;
let mut cursor = 0usize;
buf[cursor] = self.configuration_version;
cursor += 1;
buf[cursor] = ((self.general_profile_space & 0x03) << 6)
| (if self.general_tier_flag { 0x20 } else { 0 })
| (self.general_profile_idc & 0x1F);
cursor += 1;
buf[cursor..cursor + 4]
.copy_from_slice(&self.general_profile_compatibility_flags.to_be_bytes());
cursor += 4;
let ciflags = self.general_constraint_indicator_flags;
buf[cursor..cursor + 6].copy_from_slice(&[
((ciflags >> 40) & 0xFF) as u8,
((ciflags >> 32) & 0xFF) as u8,
((ciflags >> 24) & 0xFF) as u8,
((ciflags >> 16) & 0xFF) as u8,
((ciflags >> 8) & 0xFF) as u8,
(ciflags & 0xFF) as u8,
]);
cursor += 6;
buf[cursor] = self.general_level_idc;
cursor += 1;
let msid = 0xF000 | (self.min_spatial_segmentation_idc & 0x0FFF);
buf[cursor..cursor + 2].copy_from_slice(&msid.to_be_bytes());
cursor += 2;
buf[cursor] = 0xFC | (self.parallelism_type & 0x03);
cursor += 1;
buf[cursor] = 0xFC | (self.chroma_format_idc & 0x03);
cursor += 1;
buf[cursor] = 0xF8 | (self.bit_depth_luma_minus8 & 0x07);
cursor += 1;
buf[cursor] = 0xF8 | (self.bit_depth_chroma_minus8 & 0x07);
cursor += 1;
buf[cursor..cursor + 2].copy_from_slice(&self.avg_frame_rate.to_be_bytes());
cursor += 2;
buf[cursor] = ((self.constant_frame_rate & 0x03) << 6)
| ((self.num_temporal_layers & 0x07) << 3)
| (if self.temporal_id_nested { 0x04 } else { 0x00 })
| (self.length_size_minus_one & 0x03);
cursor += 1;
buf[cursor] = broadcast_common::len::fit_u8(self.arrays.len(), "numOfArrays")?;
cursor += 1;
for arr in &self.arrays {
let first_byte =
(if arr.array_completeness { 0x80u8 } else { 0 }) | (arr.nal_unit_type & 0x3F);
buf[cursor] = first_byte;
cursor += 1;
let num = broadcast_common::len::fit_u16(arr.nalus.len(), "numNalus")?;
buf[cursor..cursor + 2].copy_from_slice(&num.to_be_bytes());
cursor += 2;
for nalu in &arr.nalus {
let len = broadcast_common::len::fit_u16(nalu.0.len(), "nalUnitLength")?;
buf[cursor..cursor + 2].copy_from_slice(&len.to_be_bytes());
cursor += 2;
buf[cursor..cursor + nalu.0.len()].copy_from_slice(&nalu.0);
cursor += nalu.0.len();
}
}
Ok(cursor)
}
}
impl fmt::Display for HEVCDecoderConfigurationRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"HEVC(tier={}, profile_idc={}, level={}, lenSize={}, arrays={})",
if self.general_tier_flag {
"high"
} else {
"main"
},
self.general_profile_idc,
self.general_level_idc,
self.length_size_minus_one + 1,
self.arrays.len(),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HEVCConfigurationBox {
pub config: HEVCDecoderConfigurationRecord,
}
impl HEVCConfigurationBox {
pub fn parse_body(body: &[u8]) -> Result<Self> {
let config = HEVCDecoderConfigurationRecord::parse(body)?;
Ok(Self { config })
}
pub fn new(config: HEVCDecoderConfigurationRecord) -> Self {
Self { config }
}
}
impl Serialize for HEVCConfigurationBox {
type Error = Error;
fn serialized_len(&self) -> usize {
crate::box_types::box_len(8 + self.config.serialized_len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
need_out(buf, need)?;
let mut cursor = 0usize;
cursor += crate::box_types::write_box_header(buf, b"hvcC", need)?;
cursor += self.config.serialize_into(&mut buf[cursor..])?;
Ok(cursor)
}
}
#[cfg(test)]
mod tests {
use broadcast_common::error::InvalidValue;
use super::*;
use alloc::vec;
use broadcast_common::{Parse, Serialize};
fn make_minimal_hvcc_body() -> Vec<u8> {
let mut body = Vec::new();
body.push(0x01);
body.push(0x01);
body.extend_from_slice(&[0u8; 4]);
body.extend_from_slice(&[0u8; 6]);
body.push(93);
body.extend_from_slice(&[0xF0, 0x00]);
body.push(0xFC);
body.push(0xFD);
body.push(0xF8);
body.push(0xF8);
body.extend_from_slice(&[0u8; 2]);
body.push(0x03);
body.push(0x01);
body.push(0x80 | 32);
body.extend_from_slice(&[0x00, 0x01]);
let vps = b"\x40\x01\x0C\x01\xFF";
body.extend_from_slice(&(vps.len() as u16).to_be_bytes());
body.extend_from_slice(vps);
body
}
#[test]
fn test_hevc_config_minimal() {
let body = make_minimal_hvcc_body();
let record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
assert_eq!(record.configuration_version, 1);
assert_eq!(record.general_profile_space, 0);
assert!(!record.general_tier_flag);
assert_eq!(record.general_profile_idc, 1);
assert_eq!(record.general_level_idc, 93);
assert_eq!(record.min_spatial_segmentation_idc, 0);
assert_eq!(record.parallelism_type, 0);
assert_eq!(record.chroma_format_idc, 1);
assert_eq!(record.bit_depth_luma_minus8, 0);
assert_eq!(record.bit_depth_chroma_minus8, 0);
assert_eq!(record.avg_frame_rate, 0);
assert_eq!(record.constant_frame_rate, 0);
assert_eq!(record.num_temporal_layers, 0);
assert!(!record.temporal_id_nested);
assert_eq!(record.length_size_minus_one, 3);
assert_eq!(record.arrays.len(), 1);
assert!(record.arrays[0].array_completeness);
assert_eq!(record.arrays[0].nal_unit_type, 32);
assert_eq!(record.arrays[0].nalus.len(), 1);
assert_eq!(record.arrays[0].nalus[0].0, b"\x40\x01\x0C\x01\xFF");
}
#[test]
fn test_hevc_config_round_trip_minimal() {
let body = make_minimal_hvcc_body();
let record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
let serialized = record.try_to_bytes().unwrap();
assert_eq!(serialized, body, "hvcC round-trip must be byte-identical");
}
#[test]
fn test_hevc_config_invalid_length_size() {
let mut body = make_minimal_hvcc_body();
body[21] = 0x02 | 0x04; let err = HEVCDecoderConfigurationRecord::parse(&body).unwrap_err();
assert!(matches!(
err,
Error::InvalidValue(InvalidValue {
field: "lengthSizeMinusOne",
..
})
));
}
#[test]
fn test_hevc_config_field_mutation() {
let body = make_minimal_hvcc_body();
let mut record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
record.general_level_idc = 120;
let serialized = record.try_to_bytes().unwrap();
assert_eq!(serialized[12], 120);
}
#[test]
fn test_hevc_config_multiple_arrays() {
let mut body = make_minimal_hvcc_body();
body.clear();
body.push(0x01); body.push(0x01); body.extend_from_slice(&[0u8; 4]); body.extend_from_slice(&[0u8; 6]); body.push(93); body.extend_from_slice(&[0xF0, 0x00]); body.push(0xFC); body.push(0xFC); body.push(0xF8); body.push(0xF8); body.extend_from_slice(&[0u8; 2]); body.push(0x0B); body.push(0x03);
body.push(0x80 | 32);
body.extend_from_slice(&[0x00, 0x01]);
let vps = vec![0x40; 4];
body.extend_from_slice(&(vps.len() as u16).to_be_bytes());
body.extend_from_slice(&vps);
body.push(0x80 | 33);
body.extend_from_slice(&[0x00, 0x02]);
let sps1 = vec![0x42; 5];
body.extend_from_slice(&(sps1.len() as u16).to_be_bytes());
body.extend_from_slice(&sps1);
let sps2 = vec![0x44; 3];
body.extend_from_slice(&(sps2.len() as u16).to_be_bytes());
body.extend_from_slice(&sps2);
body.push(34);
body.extend_from_slice(&[0x00, 0x00]);
let record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
assert_eq!(record.arrays.len(), 3);
assert_eq!(record.arrays[0].nal_unit_type, 32);
assert_eq!(record.arrays[0].nalus.len(), 1);
assert_eq!(record.arrays[1].nal_unit_type, 33);
assert_eq!(record.arrays[1].nalus.len(), 2);
assert_eq!(record.arrays[2].nal_unit_type, 34);
assert_eq!(record.arrays[2].nalus.len(), 0);
let serialized = record.try_to_bytes().unwrap();
assert_eq!(serialized, body);
}
#[test]
fn test_hevc_config_nested_metadata() {
let body = make_minimal_hvcc_body();
let mut record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
record.temporal_id_nested = true;
record.num_temporal_layers = 3;
record.constant_frame_rate = 1;
record.parallelism_type = 2;
record.chroma_format_idc = 2;
let serialized = record.try_to_bytes().unwrap();
let re = HEVCDecoderConfigurationRecord::parse(&serialized).unwrap();
assert!(re.temporal_id_nested);
assert_eq!(re.num_temporal_layers, 3);
assert_eq!(re.constant_frame_rate, 1);
assert_eq!(re.parallelism_type, 2);
assert_eq!(re.chroma_format_idc, 2);
}
fn record_with_arrays(arrays: Vec<HevcNalArray>) -> HEVCDecoderConfigurationRecord {
let body = make_minimal_hvcc_body();
let mut record = HEVCDecoderConfigurationRecord::parse(&body).unwrap();
record.arrays = arrays;
record
}
#[test]
fn oversized_nalu_length_errors() {
let record = record_with_arrays(vec![HevcNalArray {
array_completeness: true,
nal_unit_type: 32,
nalus: vec![HevcNalUnit(vec![0xAAu8; 65536])],
}]);
let err = record.try_to_bytes().unwrap_err();
assert!(
matches!(
err,
Error::FieldOverflow(broadcast_common::len::FieldOverflow {
field: "nalUnitLength",
..
})
),
"expected FieldOverflow for nalUnitLength, got {err:?}"
);
}
#[test]
fn max_nalu_length_round_trips() {
let record = record_with_arrays(vec![HevcNalArray {
array_completeness: true,
nal_unit_type: 32,
nalus: vec![HevcNalUnit(vec![0xAAu8; 65535])],
}]);
let bytes = record.try_to_bytes().unwrap();
let re = HEVCDecoderConfigurationRecord::parse(&bytes).unwrap();
assert_eq!(re.arrays[0].nalus[0].0.len(), 65535);
}
#[test]
fn too_many_arrays_errors() {
let arrays: Vec<HevcNalArray> = (0..256)
.map(|_| HevcNalArray {
array_completeness: false,
nal_unit_type: 33,
nalus: vec![HevcNalUnit(vec![0x42])],
})
.collect();
let record = record_with_arrays(arrays);
let err = record.try_to_bytes().unwrap_err();
assert!(
matches!(
err,
Error::FieldOverflow(broadcast_common::len::FieldOverflow {
field: "numOfArrays",
..
})
),
"expected FieldOverflow for numOfArrays, got {err:?}"
);
}
#[test]
fn max_arrays_round_trips() {
let arrays: Vec<HevcNalArray> = (0..255)
.map(|_| HevcNalArray {
array_completeness: false,
nal_unit_type: 33,
nalus: vec![HevcNalUnit(vec![0x42])],
})
.collect();
let record = record_with_arrays(arrays);
let bytes = record.try_to_bytes().unwrap();
let re = HEVCDecoderConfigurationRecord::parse(&bytes).unwrap();
assert_eq!(re.arrays.len(), 255);
}
}