use std::borrow::Cow;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum NalError {
#[error("NAL unit is empty (no header byte)")]
EmptyNal,
#[error("forbidden_zero_bit is not 0 (got {0})")]
ForbiddenZeroBitNotZero(u8),
#[error("AVCC length prefix is truncated")]
TruncatedAvccLength,
#[error("AVCC NAL payload is shorter than the declared length")]
TruncatedAvccPayload,
#[error("AVCC nalu_length_size must be 1, 2, or 4 (got {0})")]
InvalidAvccLengthSize(u8),
#[error("NAL extension header is truncated (need {need} bytes after type {nut}, got {got})")]
TruncatedExtensionHeader { nut: u8, need: usize, got: usize },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NalUnitType {
Unspecified(u8),
SliceNonIdr,
SliceDataPartitionA,
SliceDataPartitionB,
SliceDataPartitionC,
SliceIdr,
Sei,
Sps,
Pps,
AccessUnitDelimiter,
EndOfSequence,
EndOfStream,
FillerData,
SpsExtension,
PrefixNalUnit,
SubsetSps,
DepthParameterSet,
Reserved(u8),
SliceAuxiliary,
SliceExtension,
SliceExtensionDepth,
UnspecifiedRange(u8),
}
impl NalUnitType {
pub fn from_u8(v: u8) -> Self {
match v & 0x1F {
0 => NalUnitType::Unspecified(0),
1 => NalUnitType::SliceNonIdr,
2 => NalUnitType::SliceDataPartitionA,
3 => NalUnitType::SliceDataPartitionB,
4 => NalUnitType::SliceDataPartitionC,
5 => NalUnitType::SliceIdr,
6 => NalUnitType::Sei,
7 => NalUnitType::Sps,
8 => NalUnitType::Pps,
9 => NalUnitType::AccessUnitDelimiter,
10 => NalUnitType::EndOfSequence,
11 => NalUnitType::EndOfStream,
12 => NalUnitType::FillerData,
13 => NalUnitType::SpsExtension,
14 => NalUnitType::PrefixNalUnit,
15 => NalUnitType::SubsetSps,
16 => NalUnitType::DepthParameterSet,
17 | 18 | 22 | 23 => NalUnitType::Reserved(v & 0x1F),
19 => NalUnitType::SliceAuxiliary,
20 => NalUnitType::SliceExtension,
21 => NalUnitType::SliceExtensionDepth,
24..=31 => NalUnitType::UnspecifiedRange(v & 0x1F),
_ => unreachable!(),
}
}
pub fn as_u8(self) -> u8 {
match self {
NalUnitType::Unspecified(v) => v,
NalUnitType::SliceNonIdr => 1,
NalUnitType::SliceDataPartitionA => 2,
NalUnitType::SliceDataPartitionB => 3,
NalUnitType::SliceDataPartitionC => 4,
NalUnitType::SliceIdr => 5,
NalUnitType::Sei => 6,
NalUnitType::Sps => 7,
NalUnitType::Pps => 8,
NalUnitType::AccessUnitDelimiter => 9,
NalUnitType::EndOfSequence => 10,
NalUnitType::EndOfStream => 11,
NalUnitType::FillerData => 12,
NalUnitType::SpsExtension => 13,
NalUnitType::PrefixNalUnit => 14,
NalUnitType::SubsetSps => 15,
NalUnitType::DepthParameterSet => 16,
NalUnitType::Reserved(v) => v,
NalUnitType::SliceAuxiliary => 19,
NalUnitType::SliceExtension => 20,
NalUnitType::SliceExtensionDepth => 21,
NalUnitType::UnspecifiedRange(v) => v,
}
}
pub fn is_vcl(self) -> bool {
matches!(
self,
NalUnitType::SliceNonIdr
| NalUnitType::SliceDataPartitionA
| NalUnitType::SliceDataPartitionB
| NalUnitType::SliceDataPartitionC
| NalUnitType::SliceIdr
)
}
pub fn is_idr(self) -> bool {
matches!(self, NalUnitType::SliceIdr)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NalHeader {
pub forbidden_zero_bit: u8,
pub nal_ref_idc: u8,
pub nal_unit_type: NalUnitType,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NalUnitHeaderMvcExtension {
pub non_idr_flag: u8,
pub priority_id: u8,
pub view_id: u16,
pub temporal_id: u8,
pub anchor_pic_flag: u8,
pub inter_view_flag: u8,
pub reserved_one_bit: u8,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NalUnitHeaderSvcExtension {
pub idr_flag: u8,
pub priority_id: u8,
pub no_inter_layer_pred_flag: u8,
pub dependency_id: u8,
pub quality_id: u8,
pub temporal_id: u8,
pub use_ref_base_pic_flag: u8,
pub discardable_flag: u8,
pub output_flag: u8,
pub reserved_three_2bits: u8,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NalUnitHeader3dAvcExtension {
pub view_idx: u8,
pub depth_flag: u8,
pub non_idr_flag: u8,
pub temporal_id: u8,
pub anchor_pic_flag: u8,
pub inter_view_flag: u8,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NalUnitHeaderExtension {
Mvc(NalUnitHeaderMvcExtension),
Svc(NalUnitHeaderSvcExtension),
Avc3d(NalUnitHeader3dAvcExtension),
}
#[derive(Debug, Clone)]
pub struct NalUnit<'a> {
pub header: NalHeader,
pub extension: Option<NalUnitHeaderExtension>,
pub rbsp: Cow<'a, [u8]>,
}
pub fn parse_nal_unit(payload: &[u8]) -> Result<NalUnit<'_>, NalError> {
if payload.is_empty() {
return Err(NalError::EmptyNal);
}
let header_byte = payload[0];
let forbidden_zero_bit = (header_byte >> 7) & 1;
if forbidden_zero_bit != 0 {
return Err(NalError::ForbiddenZeroBitNotZero(forbidden_zero_bit));
}
let header = NalHeader {
forbidden_zero_bit,
nal_ref_idc: (header_byte >> 5) & 3,
nal_unit_type: NalUnitType::from_u8(header_byte),
};
let raw_after_header = &payload[1..];
let (extension, ext_bytes) = match header.nal_unit_type {
NalUnitType::PrefixNalUnit | NalUnitType::SliceExtension => {
parse_extension_14_or_20(raw_after_header, header.nal_unit_type.as_u8())?
}
NalUnitType::SliceExtensionDepth => {
parse_extension_21(raw_after_header)?
}
_ => (None, 0),
};
let rbsp = rbsp_from_nal_payload(&raw_after_header[ext_bytes..]);
Ok(NalUnit {
header,
extension,
rbsp,
})
}
fn parse_extension_14_or_20(
raw: &[u8],
nut: u8,
) -> Result<(Option<NalUnitHeaderExtension>, usize), NalError> {
if raw.len() < 3 {
return Err(NalError::TruncatedExtensionHeader {
nut,
need: 3,
got: raw.len(),
});
}
let b0 = raw[0] as u32;
let b1 = raw[1] as u32;
let b2 = raw[2] as u32;
let bits24 = (b0 << 16) | (b1 << 8) | b2;
let svc_extension_flag = (bits24 >> 23) & 0x1;
let ext = if svc_extension_flag == 1 {
NalUnitHeaderExtension::Svc(parse_svc_body(bits24))
} else {
NalUnitHeaderExtension::Mvc(parse_mvc_body(bits24))
};
Ok((Some(ext), 3))
}
fn parse_extension_21(raw: &[u8]) -> Result<(Option<NalUnitHeaderExtension>, usize), NalError> {
if raw.is_empty() {
return Err(NalError::TruncatedExtensionHeader {
nut: 21,
need: 2,
got: 0,
});
}
let flag = (raw[0] >> 7) & 1;
if flag == 1 {
if raw.len() < 2 {
return Err(NalError::TruncatedExtensionHeader {
nut: 21,
need: 2,
got: raw.len(),
});
}
let bits16 = ((raw[0] as u32) << 8) | raw[1] as u32;
let ext = NalUnitHeaderExtension::Avc3d(parse_3davc_body(bits16));
Ok((Some(ext), 2))
} else {
if raw.len() < 3 {
return Err(NalError::TruncatedExtensionHeader {
nut: 21,
need: 3,
got: raw.len(),
});
}
let bits24 = ((raw[0] as u32) << 16) | ((raw[1] as u32) << 8) | raw[2] as u32;
let ext = NalUnitHeaderExtension::Mvc(parse_mvc_body(bits24));
Ok((Some(ext), 3))
}
}
fn parse_mvc_body(bits24: u32) -> NalUnitHeaderMvcExtension {
NalUnitHeaderMvcExtension {
non_idr_flag: ((bits24 >> 22) & 0x1) as u8,
priority_id: ((bits24 >> 16) & 0x3F) as u8,
view_id: ((bits24 >> 6) & 0x3FF) as u16,
temporal_id: ((bits24 >> 3) & 0x7) as u8,
anchor_pic_flag: ((bits24 >> 2) & 0x1) as u8,
inter_view_flag: ((bits24 >> 1) & 0x1) as u8,
reserved_one_bit: (bits24 & 0x1) as u8,
}
}
fn parse_svc_body(bits24: u32) -> NalUnitHeaderSvcExtension {
NalUnitHeaderSvcExtension {
idr_flag: ((bits24 >> 22) & 0x1) as u8,
priority_id: ((bits24 >> 16) & 0x3F) as u8,
no_inter_layer_pred_flag: ((bits24 >> 15) & 0x1) as u8,
dependency_id: ((bits24 >> 12) & 0x7) as u8,
quality_id: ((bits24 >> 8) & 0xF) as u8,
temporal_id: ((bits24 >> 5) & 0x7) as u8,
use_ref_base_pic_flag: ((bits24 >> 4) & 0x1) as u8,
discardable_flag: ((bits24 >> 3) & 0x1) as u8,
output_flag: ((bits24 >> 2) & 0x1) as u8,
reserved_three_2bits: (bits24 & 0x3) as u8,
}
}
fn parse_3davc_body(bits16: u32) -> NalUnitHeader3dAvcExtension {
NalUnitHeader3dAvcExtension {
view_idx: ((bits16 >> 7) & 0xFF) as u8,
depth_flag: ((bits16 >> 6) & 0x1) as u8,
non_idr_flag: ((bits16 >> 5) & 0x1) as u8,
temporal_id: ((bits16 >> 2) & 0x7) as u8,
anchor_pic_flag: ((bits16 >> 1) & 0x1) as u8,
inter_view_flag: (bits16 & 0x1) as u8,
}
}
pub fn rbsp_from_nal_payload(nal_payload: &[u8]) -> Cow<'_, [u8]> {
if !contains_emulation_byte(nal_payload) {
return Cow::Borrowed(nal_payload);
}
let mut out = Vec::with_capacity(nal_payload.len());
let mut i = 0;
while i < nal_payload.len() {
if i + 2 < nal_payload.len()
&& nal_payload[i] == 0
&& nal_payload[i + 1] == 0
&& nal_payload[i + 2] == 0x03
{
out.push(0);
out.push(0);
i += 3;
} else {
out.push(nal_payload[i]);
i += 1;
}
}
Cow::Owned(out)
}
fn contains_emulation_byte(bytes: &[u8]) -> bool {
let mut i = 0;
while i + 2 < bytes.len() {
if bytes[i] == 0 && bytes[i + 1] == 0 && bytes[i + 2] == 0x03 {
return true;
}
i += 1;
}
false
}
pub struct AnnexBSplitter<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> AnnexBSplitter<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
}
impl<'a> Iterator for AnnexBSplitter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
let start_offset = find_start_code_prefix(&self.data[self.pos..])?;
let prefix_pos = self.pos + start_offset;
let after_prefix = prefix_pos + 3;
let next_prefix = find_start_code_prefix(&self.data[after_prefix..])
.map(|off| after_prefix + off)
.unwrap_or(self.data.len());
let mut nal_end = next_prefix;
while nal_end > after_prefix && self.data[nal_end - 1] == 0 {
nal_end -= 1;
}
self.pos = next_prefix;
Some(&self.data[after_prefix..nal_end])
}
}
fn find_start_code_prefix(bytes: &[u8]) -> Option<usize> {
if bytes.len() < 3 {
return None;
}
let mut i = 0;
while i + 2 < bytes.len() {
if bytes[i] == 0 && bytes[i + 1] == 0 && bytes[i + 2] == 1 {
return Some(i);
}
i += 1;
}
None
}
#[derive(Debug)]
pub struct AvccSplitter<'a> {
data: &'a [u8],
pos: usize,
nalu_length_size: u8,
}
impl<'a> AvccSplitter<'a> {
pub fn new(data: &'a [u8], nalu_length_size: u8) -> Result<Self, NalError> {
if !matches!(nalu_length_size, 1 | 2 | 4) {
return Err(NalError::InvalidAvccLengthSize(nalu_length_size));
}
Ok(Self {
data,
pos: 0,
nalu_length_size,
})
}
}
impl<'a> Iterator for AvccSplitter<'a> {
type Item = Result<&'a [u8], NalError>;
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.data.len() {
return None;
}
let n = self.nalu_length_size as usize;
if self.pos + n > self.data.len() {
return Some(Err(NalError::TruncatedAvccLength));
}
let mut len: usize = 0;
for i in 0..n {
len = (len << 8) | self.data[self.pos + i] as usize;
}
self.pos += n;
if self.pos + len > self.data.len() {
return Some(Err(NalError::TruncatedAvccPayload));
}
let payload = &self.data[self.pos..self.pos + len];
self.pos += len;
Some(Ok(payload))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nal_header_parses_sps_byte() {
let nu = parse_nal_unit(&[0x67, 0x42, 0x00, 0x1e]).unwrap();
assert_eq!(nu.header.forbidden_zero_bit, 0);
assert_eq!(nu.header.nal_ref_idc, 3);
assert_eq!(nu.header.nal_unit_type, NalUnitType::Sps);
assert_eq!(&*nu.rbsp, &[0x42, 0x00, 0x1e]);
}
#[test]
fn nal_header_parses_idr_slice() {
let nu = parse_nal_unit(&[0x65, 0x88]).unwrap();
assert_eq!(nu.header.nal_unit_type, NalUnitType::SliceIdr);
assert!(nu.header.nal_unit_type.is_vcl());
assert!(nu.header.nal_unit_type.is_idr());
}
#[test]
fn nal_header_parses_non_idr_slice() {
let nu = parse_nal_unit(&[0x41, 0x9a]).unwrap();
assert_eq!(nu.header.nal_ref_idc, 2);
assert_eq!(nu.header.nal_unit_type, NalUnitType::SliceNonIdr);
assert!(nu.header.nal_unit_type.is_vcl());
assert!(!nu.header.nal_unit_type.is_idr());
}
#[test]
fn nal_header_rejects_forbidden_zero_bit_set() {
assert_eq!(
parse_nal_unit(&[0xe7, 0x00]).unwrap_err(),
NalError::ForbiddenZeroBitNotZero(1)
);
}
#[test]
fn nal_header_rejects_empty_input() {
assert_eq!(parse_nal_unit(&[]).unwrap_err(), NalError::EmptyNal);
}
#[test]
fn nal_unit_type_round_trip_for_table_7_1() {
for v in 0u8..=31 {
let t = NalUnitType::from_u8(v);
assert_eq!(t.as_u8(), v, "round-trip failed for nal_unit_type {}", v);
}
}
#[test]
fn rbsp_no_emulation_borrows() {
let input: &[u8] = &[0x00, 0x01, 0x02, 0x04];
let cow = rbsp_from_nal_payload(input);
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(&*cow, input);
}
#[test]
fn rbsp_strips_emulation_byte_in_middle() {
let cow = rbsp_from_nal_payload(&[0x00, 0x00, 0x03, 0x04]);
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(&*cow, &[0x00, 0x00, 0x04]);
}
#[test]
fn rbsp_strips_multiple_emulation_bytes() {
let cow = rbsp_from_nal_payload(&[0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x01]);
assert_eq!(&*cow, &[0x00, 0x00, 0x00, 0x00, 0x01]);
}
#[test]
fn rbsp_keeps_unrelated_03_bytes() {
let cow = rbsp_from_nal_payload(&[0x03, 0x00, 0x03, 0x05]);
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(&*cow, &[0x03, 0x00, 0x03, 0x05]);
}
#[test]
fn annex_b_splits_three_byte_start_codes() {
let stream = [0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, 0x01, 0xcc, 0xdd];
let nals: Vec<_> = AnnexBSplitter::new(&stream).collect();
assert_eq!(nals, vec![&[0xaa, 0xbb][..], &[0xcc, 0xdd][..]]);
}
#[test]
fn annex_b_splits_four_byte_start_codes() {
let stream = [
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, 0x00, 0x01, 0xcc, 0xdd,
];
let nals: Vec<_> = AnnexBSplitter::new(&stream).collect();
assert_eq!(nals, vec![&[0xaa, 0xbb][..], &[0xcc, 0xdd][..]]);
}
#[test]
fn annex_b_handles_leading_zeros_then_three_byte() {
let stream = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb];
let nals: Vec<_> = AnnexBSplitter::new(&stream).collect();
assert_eq!(nals, vec![&[0xaa, 0xbb][..]]);
}
#[test]
fn annex_b_strips_trailing_zeros() {
let stream = [
0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x01, 0xcc,
];
let nals: Vec<_> = AnnexBSplitter::new(&stream).collect();
assert_eq!(nals, vec![&[0xaa, 0xbb][..], &[0xcc][..]]);
}
#[test]
fn annex_b_empty_yields_nothing() {
let nals: Vec<_> = AnnexBSplitter::new(&[]).collect();
assert!(nals.is_empty());
let nals: Vec<_> = AnnexBSplitter::new(&[0x00, 0x00, 0x00]).collect();
assert!(nals.is_empty());
}
#[test]
fn avcc_splits_with_4_byte_length() {
let stream = [
0x00, 0x00, 0x00, 0x02, 0xaa, 0xbb, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xdd, 0xee,
];
let nals: Vec<_> = AvccSplitter::new(&stream, 4)
.unwrap()
.map(|r| r.unwrap().to_vec())
.collect();
assert_eq!(nals, vec![vec![0xaa, 0xbb], vec![0xcc, 0xdd, 0xee]]);
}
#[test]
fn avcc_truncated_payload_errors() {
let stream = [0x00, 0x00, 0x00, 0x05, 0xaa, 0xbb];
let mut split = AvccSplitter::new(&stream, 4).unwrap();
assert_eq!(
split.next().unwrap().unwrap_err(),
NalError::TruncatedAvccPayload
);
}
#[test]
fn avcc_truncated_length_errors() {
let stream = [0x00, 0x00];
let mut split = AvccSplitter::new(&stream, 4).unwrap();
assert_eq!(
split.next().unwrap().unwrap_err(),
NalError::TruncatedAvccLength
);
}
#[test]
fn avcc_rejects_invalid_length_size() {
assert_eq!(
AvccSplitter::new(&[], 3).unwrap_err(),
NalError::InvalidAvccLengthSize(3)
);
}
#[test]
fn round_trip_annex_b_then_parse_with_emulation() {
let stream = [0x00, 0x00, 0x01, 0x67, 0x00, 0x00, 0x03, 0x05];
let mut split = AnnexBSplitter::new(&stream);
let nal_bytes = split.next().unwrap();
assert_eq!(nal_bytes, &[0x67, 0x00, 0x00, 0x03, 0x05]);
let nu = parse_nal_unit(nal_bytes).unwrap();
assert_eq!(nu.header.nal_unit_type, NalUnitType::Sps);
assert_eq!(&*nu.rbsp, &[0x00, 0x00, 0x05]);
assert!(nu.extension.is_none());
}
fn pack_mvc_ext(
non_idr: u8,
priority: u8,
view: u16,
temporal: u8,
anchor: u8,
inter_view: u8,
reserved: u8,
) -> [u8; 3] {
let bits24: u32 = ((non_idr as u32 & 0x1) << 22)
| ((priority as u32 & 0x3F) << 16)
| ((view as u32 & 0x3FF) << 6)
| ((temporal as u32 & 0x7) << 3)
| ((anchor as u32 & 0x1) << 2)
| ((inter_view as u32 & 0x1) << 1)
| (reserved as u32 & 0x1);
[
((bits24 >> 16) & 0xFF) as u8,
((bits24 >> 8) & 0xFF) as u8,
(bits24 & 0xFF) as u8,
]
}
#[test]
fn mvc_extension_parses_via_prefix_nal_14() {
let ext = pack_mvc_ext(1, 0, 0, 0, 1, 1, 1);
let mut nal = vec![0x0E]; nal.extend_from_slice(&ext);
nal.extend_from_slice(&[0xAA, 0xBB]); let nu = parse_nal_unit(&nal).unwrap();
assert_eq!(nu.header.nal_unit_type, NalUnitType::PrefixNalUnit);
match nu.extension {
Some(NalUnitHeaderExtension::Mvc(m)) => {
assert_eq!(m.non_idr_flag, 1);
assert_eq!(m.priority_id, 0);
assert_eq!(m.view_id, 0);
assert_eq!(m.temporal_id, 0);
assert_eq!(m.anchor_pic_flag, 1);
assert_eq!(m.inter_view_flag, 1);
assert_eq!(m.reserved_one_bit, 1);
}
other => panic!("expected MVC extension, got {:?}", other),
}
assert_eq!(&*nu.rbsp, &[0xAA, 0xBB]);
}
#[test]
fn mvc_extension_handles_max_field_values() {
let ext = pack_mvc_ext(0, 63, 1023, 7, 0, 0, 0);
let mut nal = vec![0x14]; nal.extend_from_slice(&ext);
let nu = parse_nal_unit(&nal).unwrap();
assert_eq!(nu.header.nal_unit_type, NalUnitType::SliceExtension);
match nu.extension {
Some(NalUnitHeaderExtension::Mvc(m)) => {
assert_eq!(m.priority_id, 63);
assert_eq!(m.view_id, 1023);
assert_eq!(m.temporal_id, 7);
}
other => panic!("expected MVC extension, got {:?}", other),
}
}
#[allow(clippy::too_many_arguments)]
fn pack_svc_ext(
idr: u8,
priority: u8,
no_inter: u8,
dependency: u8,
quality: u8,
temporal: u8,
use_ref: u8,
discardable: u8,
output_flag: u8,
reserved: u8,
) -> [u8; 3] {
let bits24: u32 = (1u32 << 23) | ((idr as u32 & 0x1) << 22)
| ((priority as u32 & 0x3F) << 16)
| ((no_inter as u32 & 0x1) << 15)
| ((dependency as u32 & 0x7) << 12)
| ((quality as u32 & 0xF) << 8)
| ((temporal as u32 & 0x7) << 5)
| ((use_ref as u32 & 0x1) << 4)
| ((discardable as u32 & 0x1) << 3)
| ((output_flag as u32 & 0x1) << 2)
| (reserved as u32 & 0x3);
[
((bits24 >> 16) & 0xFF) as u8,
((bits24 >> 8) & 0xFF) as u8,
(bits24 & 0xFF) as u8,
]
}
#[test]
fn svc_extension_parses_via_prefix_nal_14() {
let ext = pack_svc_ext(1, 5, 0, 2, 3, 4, 1, 0, 1, 3);
let mut nal = vec![0x0E]; nal.extend_from_slice(&ext);
let nu = parse_nal_unit(&nal).unwrap();
match nu.extension {
Some(NalUnitHeaderExtension::Svc(s)) => {
assert_eq!(s.idr_flag, 1);
assert_eq!(s.priority_id, 5);
assert_eq!(s.no_inter_layer_pred_flag, 0);
assert_eq!(s.dependency_id, 2);
assert_eq!(s.quality_id, 3);
assert_eq!(s.temporal_id, 4);
assert_eq!(s.use_ref_base_pic_flag, 1);
assert_eq!(s.discardable_flag, 0);
assert_eq!(s.output_flag, 1);
assert_eq!(s.reserved_three_2bits, 3);
}
other => panic!("expected SVC extension, got {:?}", other),
}
}
#[test]
fn svc_extension_handles_max_field_values() {
let ext = pack_svc_ext(0, 63, 1, 7, 15, 7, 0, 1, 0, 3);
let mut nal = vec![0x14];
nal.extend_from_slice(&ext);
let nu = parse_nal_unit(&nal).unwrap();
match nu.extension {
Some(NalUnitHeaderExtension::Svc(s)) => {
assert_eq!(s.priority_id, 63);
assert_eq!(s.dependency_id, 7);
assert_eq!(s.quality_id, 15);
assert_eq!(s.temporal_id, 7);
}
other => panic!("expected SVC extension, got {:?}", other),
}
}
fn pack_3davc_ext(
view_idx: u8,
depth: u8,
non_idr: u8,
temporal: u8,
anchor: u8,
inter_view: u8,
) -> [u8; 2] {
let bits16: u32 = (1u32 << 15) | ((view_idx as u32 & 0xFF) << 7)
| ((depth as u32 & 0x1) << 6)
| ((non_idr as u32 & 0x1) << 5)
| ((temporal as u32 & 0x7) << 2)
| ((anchor as u32 & 0x1) << 1)
| (inter_view as u32 & 0x1);
[((bits16 >> 8) & 0xFF) as u8, (bits16 & 0xFF) as u8]
}
#[test]
fn avc_3d_extension_parses_via_slice_extension_depth() {
let ext = pack_3davc_ext(5, 1, 0, 3, 1, 1);
let mut nal = vec![0x15]; nal.extend_from_slice(&ext);
nal.extend_from_slice(&[0xAA, 0xBB]);
let nu = parse_nal_unit(&nal).unwrap();
assert_eq!(nu.header.nal_unit_type, NalUnitType::SliceExtensionDepth);
match nu.extension {
Some(NalUnitHeaderExtension::Avc3d(a)) => {
assert_eq!(a.view_idx, 5);
assert_eq!(a.depth_flag, 1);
assert_eq!(a.non_idr_flag, 0);
assert_eq!(a.temporal_id, 3);
assert_eq!(a.anchor_pic_flag, 1);
assert_eq!(a.inter_view_flag, 1);
}
other => panic!("expected 3D-AVC extension, got {:?}", other),
}
assert_eq!(&*nu.rbsp, &[0xAA, 0xBB]);
}
#[test]
fn slice_extension_depth_with_avc3d_flag_zero_falls_through_to_mvc() {
let ext = pack_mvc_ext(1, 1, 7, 2, 0, 1, 1);
let mut nal = vec![0x15];
nal.extend_from_slice(&ext);
let nu = parse_nal_unit(&nal).unwrap();
match nu.extension {
Some(NalUnitHeaderExtension::Mvc(m)) => {
assert_eq!(m.non_idr_flag, 1);
assert_eq!(m.priority_id, 1);
assert_eq!(m.view_id, 7);
assert_eq!(m.temporal_id, 2);
assert_eq!(m.anchor_pic_flag, 0);
assert_eq!(m.inter_view_flag, 1);
assert_eq!(m.reserved_one_bit, 1);
}
other => panic!("expected MVC extension fallthrough, got {:?}", other),
}
}
#[test]
fn extension_header_truncated_three_byte_prefix() {
let nal = [0x0E, 0xAB, 0xCD];
let err = parse_nal_unit(&nal).unwrap_err();
assert_eq!(
err,
NalError::TruncatedExtensionHeader {
nut: 14,
need: 3,
got: 2,
}
);
}
#[test]
fn extension_header_truncated_two_byte_3davc() {
let nal = [0x15, 0x80];
let err = parse_nal_unit(&nal).unwrap_err();
assert_eq!(
err,
NalError::TruncatedExtensionHeader {
nut: 21,
need: 2,
got: 1,
}
);
}
#[test]
fn extension_header_truncated_zero_after_header() {
let nal = [0x15];
let err = parse_nal_unit(&nal).unwrap_err();
assert_eq!(
err,
NalError::TruncatedExtensionHeader {
nut: 21,
need: 2,
got: 0,
}
);
let nal = [0x0E];
let err = parse_nal_unit(&nal).unwrap_err();
assert_eq!(
err,
NalError::TruncatedExtensionHeader {
nut: 14,
need: 3,
got: 0,
}
);
}
#[test]
fn extension_bytes_are_not_emulation_prevention_stripped() {
let nal = [
0x0E, 0x00, 0x00, 0x03, 0xAA, 0xBB,
];
let nu = parse_nal_unit(&nal).unwrap();
match nu.extension {
Some(NalUnitHeaderExtension::Mvc(m)) => {
assert_eq!(m.non_idr_flag, 0);
assert_eq!(m.priority_id, 0);
assert_eq!(m.view_id, 0);
assert_eq!(m.temporal_id, 0);
assert_eq!(m.anchor_pic_flag, 0);
assert_eq!(m.inter_view_flag, 1);
assert_eq!(m.reserved_one_bit, 1);
}
other => panic!("expected MVC extension, got {:?}", other),
}
assert_eq!(&*nu.rbsp, &[0xAA, 0xBB]);
}
#[test]
fn extension_followed_by_emulation_prevention_in_rbsp() {
let mvc = pack_mvc_ext(0, 0, 0, 0, 0, 0, 0);
let mut nal = vec![0x0E];
nal.extend_from_slice(&mvc);
nal.extend_from_slice(&[0x00, 0x00, 0x03, 0x04]);
let nu = parse_nal_unit(&nal).unwrap();
assert!(matches!(nu.extension, Some(NalUnitHeaderExtension::Mvc(_))));
assert_eq!(&*nu.rbsp, &[0x00, 0x00, 0x04]);
}
#[test]
fn non_extension_nal_types_carry_no_extension_field() {
let nu = parse_nal_unit(&[0x67, 0x42, 0x00, 0x1e]).unwrap();
assert!(nu.extension.is_none());
let nu = parse_nal_unit(&[0x41, 0x9a]).unwrap();
assert!(nu.extension.is_none());
}
}