use crate::annexb::{iter_annexb_nals, iter_length_prefixed_nals};
const AVC_NAL_TYPE_MASK: u8 = 0x1F;
const AVC_NAL_IDR: u8 = 5;
const AVC_NAL_SEI: u8 = 6;
const AVC_NAL_SPS: u8 = 7;
const AVC_SEI_PAYLOAD_TYPE_RECOVERY_POINT: u32 = 6;
const HEVC_NAL_TYPE_SHIFT: u8 = 1;
const HEVC_NAL_TYPE_MASK: u8 = 0x3F;
const HEVC_IRAP_FIRST: u8 = 16;
const HEVC_IRAP_LAST: u8 = 23;
const VVC_NAL_TYPE_SHIFT: u8 = 3;
const VVC_NAL_TYPE_MASK: u8 = 0x1F;
const VVC_NAL_IDR_W_RADL: u8 = 7;
const VVC_NAL_IDR_N_LP: u8 = 8;
const VVC_NAL_CRA: u8 = 9;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum NalCodec {
Avc,
Hevc,
Vvc,
}
impl NalCodec {
pub fn name(&self) -> &'static str {
match self {
NalCodec::Avc => "AVC",
NalCodec::Hevc => "HEVC",
NalCodec::Vvc => "VVC",
}
}
fn header_len(self) -> usize {
match self {
NalCodec::Avc => 1,
NalCodec::Hevc | NalCodec::Vvc => 2,
}
}
}
broadcast_common::impl_spec_display!(NalCodec);
pub fn nal_unit_type(codec: NalCodec, nal: &[u8]) -> Option<u8> {
if nal.len() < codec.header_len() {
return None;
}
Some(match codec {
NalCodec::Avc => nal[0] & AVC_NAL_TYPE_MASK,
NalCodec::Hevc => (nal[0] >> HEVC_NAL_TYPE_SHIFT) & HEVC_NAL_TYPE_MASK,
NalCodec::Vvc => (nal[1] >> VVC_NAL_TYPE_SHIFT) & VVC_NAL_TYPE_MASK,
})
}
pub fn is_keyframe_nal(codec: NalCodec, nal: &[u8]) -> bool {
match nal_unit_type(codec, nal) {
None => false,
Some(t) => match codec {
NalCodec::Avc => t == AVC_NAL_IDR,
NalCodec::Hevc => (HEVC_IRAP_FIRST..=HEVC_IRAP_LAST).contains(&t),
NalCodec::Vvc => t == VVC_NAL_IDR_W_RADL || t == VVC_NAL_IDR_N_LP || t == VVC_NAL_CRA,
},
}
}
pub fn access_unit_is_keyframe(codec: NalCodec, au: &[u8], length_prefixed: bool) -> bool {
if length_prefixed {
match iter_length_prefixed_nals(au) {
Ok(nals) => nals.iter().any(|nal| is_keyframe_nal(codec, nal)),
Err(_) => false,
}
} else {
iter_annexb_nals(au).any(|nal| is_keyframe_nal(codec, nal))
}
}
struct EbspBytes<'a> {
nal: &'a [u8],
pos: usize,
zero_run: u8,
}
impl<'a> EbspBytes<'a> {
fn new(nal: &'a [u8]) -> Self {
Self {
nal,
pos: 0,
zero_run: 0,
}
}
}
impl Iterator for EbspBytes<'_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
while self.pos < self.nal.len() {
let b = self.nal[self.pos];
self.pos += 1;
if self.zero_run >= 2 && b == 0x03 {
self.zero_run = 0;
continue;
}
self.zero_run = if b == 0 { self.zero_run + 1 } else { 0 };
return Some(b);
}
None
}
}
fn read_sei_varint(bytes: &mut impl Iterator<Item = u8>) -> Option<u32> {
let mut value: u32 = 0;
loop {
let b = bytes.next()?;
value += u32::from(b);
if b != 0xFF {
return Some(value);
}
}
}
pub fn recovery_point_sei(nal: &[u8]) -> bool {
if nal_unit_type(NalCodec::Avc, nal) != Some(AVC_NAL_SEI) {
return false;
}
let mut bytes = EbspBytes::new(&nal[NalCodec::Avc.header_len()..]).peekable();
loop {
let Some(payload_type) = read_sei_varint(&mut bytes) else {
return false;
};
let Some(payload_size) = read_sei_varint(&mut bytes) else {
return false;
};
if payload_type == AVC_SEI_PAYLOAD_TYPE_RECOVERY_POINT {
return true;
}
for _ in 0..payload_size {
if bytes.next().is_none() {
return false;
}
}
if bytes.peek().is_none() {
return false;
}
}
}
pub fn access_unit_is_rap(codec: NalCodec, au: &[u8], length_prefixed: bool) -> bool {
match codec {
NalCodec::Hevc | NalCodec::Vvc => access_unit_is_keyframe(codec, au, length_prefixed),
NalCodec::Avc => {
let is_rap_nal = |nal: &[u8]| {
nal_unit_type(NalCodec::Avc, nal) == Some(AVC_NAL_SPS)
|| is_keyframe_nal(NalCodec::Avc, nal)
|| recovery_point_sei(nal)
};
if length_prefixed {
match iter_length_prefixed_nals(au) {
Ok(nals) => nals.iter().any(|nal| is_rap_nal(nal)),
Err(_) => false,
}
} else {
iter_annexb_nals(au).any(is_rap_nal)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn avc_nal_type_extraction() {
assert_eq!(nal_unit_type(NalCodec::Avc, &[0x67, 0x42]), Some(7));
assert_eq!(nal_unit_type(NalCodec::Avc, &[0x65, 0x88]), Some(5));
assert!(is_keyframe_nal(NalCodec::Avc, &[0x65, 0x88]));
assert!(!is_keyframe_nal(NalCodec::Avc, &[0x67, 0x42])); assert!(!is_keyframe_nal(NalCodec::Avc, &[0x41, 0x9a]));
assert_eq!(nal_unit_type(NalCodec::Avc, &[]), None);
assert!(!is_keyframe_nal(NalCodec::Avc, &[]));
}
#[test]
fn hevc_nal_type_extraction() {
assert_eq!(nal_unit_type(NalCodec::Hevc, &[0x42, 0x01]), Some(33));
assert!(!is_keyframe_nal(NalCodec::Hevc, &[0x42, 0x01]));
assert_eq!(nal_unit_type(NalCodec::Hevc, &[0x26, 0x01]), Some(19));
assert_eq!(nal_unit_type(NalCodec::Hevc, &[0x28, 0x01]), Some(20));
assert!(is_keyframe_nal(NalCodec::Hevc, &[0x26, 0x01]));
assert!(is_keyframe_nal(NalCodec::Hevc, &[0x28, 0x01]));
assert_eq!(nal_unit_type(NalCodec::Hevc, &[0x02, 0x01]), Some(1));
assert!(!is_keyframe_nal(NalCodec::Hevc, &[0x02, 0x01]));
assert_eq!(nal_unit_type(NalCodec::Hevc, &[0x26]), None);
}
#[test]
fn vvc_nal_type_extraction() {
assert_eq!(nal_unit_type(NalCodec::Vvc, &[0x00, 0x78]), Some(15));
assert!(!is_keyframe_nal(NalCodec::Vvc, &[0x00, 0x78]));
assert_eq!(nal_unit_type(NalCodec::Vvc, &[0x00, 0x38]), Some(7));
assert_eq!(nal_unit_type(NalCodec::Vvc, &[0x00, 0x40]), Some(8));
assert_eq!(nal_unit_type(NalCodec::Vvc, &[0x00, 0x48]), Some(9));
assert!(is_keyframe_nal(NalCodec::Vvc, &[0x00, 0x38]));
assert!(is_keyframe_nal(NalCodec::Vvc, &[0x00, 0x40]));
assert!(is_keyframe_nal(NalCodec::Vvc, &[0x00, 0x48]));
assert!(!is_keyframe_nal(NalCodec::Vvc, &[0x00, 0x00]));
}
#[test]
fn access_unit_annexb_and_length_prefixed_agree() {
let non_kf = [0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x00, 0x01, 0x41, 0x9a];
assert!(!access_unit_is_keyframe(NalCodec::Avc, &non_kf, false));
let kf = [0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x00, 0x01, 0x65, 0x88];
assert!(access_unit_is_keyframe(NalCodec::Avc, &kf, false));
let lp_non_kf = crate::annexb::annexb_to_length_prefixed(&non_kf);
let lp_kf = crate::annexb::annexb_to_length_prefixed(&kf);
assert!(!access_unit_is_keyframe(NalCodec::Avc, &lp_non_kf, true));
assert!(access_unit_is_keyframe(NalCodec::Avc, &lp_kf, true));
}
#[test]
fn malformed_length_prefixed_is_not_keyframe() {
let lp = [0x00, 0x00, 0x00, 0x63, 0x65, 0x88];
assert!(!access_unit_is_keyframe(NalCodec::Avc, &lp, true));
}
#[test]
fn nal_codec_display() {
assert_eq!(NalCodec::Avc.to_string(), "AVC");
assert_eq!(NalCodec::Hevc.to_string(), "HEVC");
assert_eq!(NalCodec::Vvc.to_string(), "VVC");
}
#[test]
fn recovery_point_sei_matches_payload_type_6() {
let nal = [0x06, 0x06, 0x00];
assert!(recovery_point_sei(&nal));
}
#[test]
fn other_sei_payload_type_is_not_recovery_point() {
let nal = [0x06, 0x01, 0x00];
assert!(!recovery_point_sei(&nal));
}
#[test]
fn recovery_point_sei_found_among_multiple_messages() {
let nal = [0x06, 0x00, 0x01, 0xAA, 0x06, 0x00];
assert!(recovery_point_sei(&nal));
}
#[test]
fn recovery_point_sei_rejects_non_sei_nal() {
let nal = [0x67, 0x42, 0x00];
assert!(!recovery_point_sei(&nal));
let short_sei = [0x06];
assert!(!recovery_point_sei(&short_sei));
}
#[test]
fn recovery_point_sei_unescapes_emulation_prevention() {
let nal = [
0x06, 0x00, 0x02, 0x00, 0x00,
0x03, 0x06, 0x00, ];
assert!(recovery_point_sei(&nal));
}
#[test]
fn access_unit_is_rap_recognises_open_gop_signals() {
let sps_led = [
0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x68, 0xCE, 0x3C, 0x80, 0x00, 0x00, 0x01, 0x41, 0x9A, ];
assert!(access_unit_is_rap(NalCodec::Avc, &sps_led, false));
assert!(!access_unit_is_keyframe(NalCodec::Avc, &sps_led, false));
let sei_led = [
0x00, 0x00, 0x01, 0x06, 0x06, 0x00, 0x80, 0x00, 0x00, 0x01, 0x41, 0x9A, ];
assert!(access_unit_is_rap(NalCodec::Avc, &sei_led, false));
assert!(!access_unit_is_keyframe(NalCodec::Avc, &sei_led, false));
let plain = [0x00, 0x00, 0x01, 0x41, 0x9A];
assert!(!access_unit_is_rap(NalCodec::Avc, &plain, false));
let idr = [0x00, 0x00, 0x01, 0x65, 0x88];
assert!(access_unit_is_rap(NalCodec::Avc, &idr, false));
assert!(access_unit_is_keyframe(NalCodec::Avc, &idr, false));
}
#[test]
fn access_unit_is_rap_hevc_matches_keyframe_helper() {
let cra = [0x00, 0x00, 0x01, 0x2A, 0x01]; assert!(access_unit_is_keyframe(NalCodec::Hevc, &cra, false));
assert!(access_unit_is_rap(NalCodec::Hevc, &cra, false));
let trail = [0x00, 0x00, 0x01, 0x02, 0x01]; assert!(!access_unit_is_keyframe(NalCodec::Hevc, &trail, false));
assert!(!access_unit_is_rap(NalCodec::Hevc, &trail, false));
}
}