mod import;
pub use import::*;
use anyhow::Context;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use scuffle_h265::{NALUnitType, SpsNALUnit};
pub struct Hvc1 {
hvcc: Option<Bytes>,
vps: Option<Bytes>,
sps: Option<Bytes>,
pps: Option<Bytes>,
}
impl Default for Hvc1 {
fn default() -> Self {
Self::new()
}
}
impl Hvc1 {
pub fn new() -> Self {
Self {
hvcc: None,
vps: None,
sps: None,
pps: None,
}
}
pub fn hvcc(&self) -> Option<&Bytes> {
self.hvcc.as_ref()
}
pub fn transform(&mut self, payload: Bytes) -> anyhow::Result<Option<Bytes>> {
let mut buf = payload.clone();
let mut nal_iter = crate::codec::annexb::NalIterator::new(&mut buf);
let mut out = BytesMut::with_capacity(payload.remaining());
let mut params_changed = false;
let mut emitted_any_slice = false;
loop {
let nal = match nal_iter.next() {
Some(Ok(n)) => n,
Some(Err(e)) => return Err(e),
None => break,
};
if self.process_nal(&nal, &mut out, &mut params_changed)? {
emitted_any_slice = true;
}
}
if let Some(nal) = nal_iter.flush()? {
let was_slice = self.process_nal(&nal, &mut out, &mut params_changed)?;
if was_slice {
emitted_any_slice = true;
}
}
if params_changed {
self.rebuild_hvcc()?;
}
if !emitted_any_slice {
return Ok(None);
}
Ok(Some(out.freeze()))
}
fn process_nal(&mut self, nal: &Bytes, out: &mut BytesMut, params_changed: &mut bool) -> anyhow::Result<bool> {
if nal.is_empty() {
return Ok(false);
}
let nal_unit_type = (nal[0] >> 1) & 0x3f;
let nal_type = NALUnitType::from(nal_unit_type);
match nal_type {
NALUnitType::VpsNut => {
if self.vps.as_deref() != Some(nal.as_ref()) {
self.vps = Some(nal.clone());
*params_changed = true;
}
Ok(false)
}
NALUnitType::SpsNut => {
if self.sps.as_deref() != Some(nal.as_ref()) {
self.sps = Some(nal.clone());
*params_changed = true;
}
Ok(false)
}
NALUnitType::PpsNut => {
if self.pps.as_deref() != Some(nal.as_ref()) {
self.pps = Some(nal.clone());
*params_changed = true;
}
Ok(false)
}
_ => {
let len = u32::try_from(nal.len()).context("NAL too large for 4-byte length prefix")?;
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(nal);
Ok(true)
}
}
}
fn rebuild_hvcc(&mut self) -> anyhow::Result<()> {
let (Some(vps), Some(sps), Some(pps)) = (&self.vps, &self.sps, &self.pps) else {
return Ok(());
};
self.hvcc = Some(build_hvcc(vps, sps, pps)?);
Ok(())
}
}
pub(crate) fn build_hvcc(vps_nal: &[u8], sps_nal: &[u8], pps_nal: &[u8]) -> anyhow::Result<Bytes> {
for (label, nal) in [("VPS", vps_nal), ("SPS", sps_nal), ("PPS", pps_nal)] {
anyhow::ensure!(
nal.len() <= u16::MAX as usize,
"{} too large for hvcC length field ({} > {})",
label,
nal.len(),
u16::MAX
);
}
let sps = SpsNALUnit::parse(&mut &sps_nal[..]).context("failed to parse SPS NAL unit for hvcC")?;
let profile = &sps.rbsp.profile_tier_level.general_profile;
let level_idc = profile.level_idc.context("missing level_idc in SPS")?;
let constraint_flags = pack_constraint_flags(profile);
let compat = profile.profile_compatibility_flag.bits().to_be_bytes();
let num_temporal_layers = sps.rbsp.sps_max_sub_layers_minus1 + 1;
let mut out = BytesMut::with_capacity(23 + vps_nal.len() + sps_nal.len() + pps_nal.len() + 9 * 3);
out.put_u8(1); out.put_u8(((profile.profile_space & 0x3) << 6) | ((profile.tier_flag as u8) << 5) | (profile.profile_idc & 0x1f));
out.put_slice(&compat);
out.put_slice(&constraint_flags);
out.put_u8(level_idc);
out.put_u16(0xf000); out.put_u8(0xfc); out.put_u8(0xfc | (sps.rbsp.chroma_format_idc & 0x3));
out.put_u8(0xf8 | (sps.rbsp.bit_depth_luma_minus8 & 0x7));
out.put_u8(0xf8 | (sps.rbsp.bit_depth_chroma_minus8 & 0x7));
out.put_u16(0); out.put_u8(((num_temporal_layers & 0x7) << 3) | ((sps.rbsp.sps_temporal_id_nesting_flag as u8) << 2) | 0x3);
out.put_u8(3);
for (nal_type, nal) in [
(u8::from(NALUnitType::VpsNut), vps_nal),
(u8::from(NALUnitType::SpsNut), sps_nal),
(u8::from(NALUnitType::PpsNut), pps_nal),
] {
out.put_u8(0x80 | (nal_type & 0x3f)); out.put_u16(1); out.put_u16(nal.len() as u16);
out.put_slice(nal);
}
Ok(out.freeze())
}
pub(crate) fn pack_constraint_flags(profile: &scuffle_h265::Profile) -> [u8; 6] {
let mut flags = [0u8; 6];
flags[0] = ((profile.progressive_source_flag as u8) << 7)
| ((profile.interlaced_source_flag as u8) << 6)
| ((profile.non_packed_constraint_flag as u8) << 5)
| ((profile.frame_only_constraint_flag as u8) << 4);
flags
}