use super::super::nal_framing::{
push_length_prefixed, walk_annexb, walk_length_prefixed, NAL_LENGTH_SIZE, NAL_PPS, NAL_SPS,
};
use crate::error::PacketSinkError;
const NAL_SPS_EXT: u8 = 13;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParameterSets {
pub(crate) sps: Vec<Vec<u8>>,
pub(crate) pps: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct ConfigFingerprint {
sps: Vec<(u32, Vec<u8>)>,
pps: Vec<(u32, PpsSlot)>,
extension: Option<(u8, u8, u8)>,
sps_ext: Vec<Vec<u8>>,
}
type PpsSlot = (Vec<u8>, Vec<u8>);
impl ConfigFingerprint {
fn put<V>(slots: &mut Vec<(u32, V)>, id: u32, value: V) {
match slots.binary_search_by_key(&id, |(slot, _)| *slot) {
Ok(i) => slots[i].1 = value,
Err(i) => slots.insert(i, (id, value)),
}
}
fn put_sps(&mut self, id: u32, nal: &[u8]) {
Self::put(&mut self.sps, id, nal[1..].to_vec());
}
fn put_pps(&mut self, id: u32, nal: &[u8], bound_sps: &[u8]) {
Self::put(
&mut self.pps,
id,
(nal[1..].to_vec(), bound_sps[1..].to_vec()),
);
}
fn has_active_sps(&self, nal: &[u8]) -> bool {
self.sps
.iter()
.any(|(_, payload)| payload.as_slice() == &nal[1..])
}
fn has_active_pps(&self, nal: &[u8]) -> bool {
self.pps
.iter()
.any(|(_, (payload, _))| payload.as_slice() == &nal[1..])
}
}
#[derive(Debug)]
pub(crate) struct AvcConfig {
pub(crate) sets: ParameterSets,
pub(crate) fingerprint: ConfigFingerprint,
}
pub(crate) fn parse_parameter_sets(extradata: &[u8]) -> Result<AvcConfig, String> {
if extradata.first() == Some(&1) {
parse_avcc_parameter_sets(extradata)
} else {
let mut sets = ParameterSets {
sps: Vec::new(),
pps: Vec::new(),
};
let mut fingerprint = ConfigFingerprint::default();
let mut summaries: Vec<SpsSummary> = Vec::new();
let mut error: Option<String> = None;
walk_annexb(extradata, |nal| {
if error.is_some() {
return;
}
if nal[0] & 0x80 != 0 {
error = Some(format!(
"configuration NAL header 0x{:02X} has forbidden_zero_bit set",
nal[0]
));
return;
}
match nal[0] & 0x1F {
NAL_SPS => match parse_sps(nal) {
Ok(summary) => {
fingerprint.put_sps(summary.sps_id, nal);
summaries.push(summary);
sets.sps.push(nal.to_vec());
}
Err(e) => error = Some(format!("SPS: {e}")),
},
NAL_PPS => match parse_pps(nal, &summaries) {
Ok((pps_id, bound)) => {
fingerprint.put_pps(pps_id, nal, &sets.sps[bound]);
sets.pps.push(nal.to_vec());
}
Err(e) => error = Some(format!("PPS: {e}")),
},
other => {
error = Some(format!(
"unexpected NAL type {other} in configuration data (expected SPS/PPS)"
));
}
}
})?;
if let Some(reason) = error {
return Err(reason);
}
if sets.sps.is_empty() || sets.pps.is_empty() {
return Err("configuration data lacks an SPS or a PPS".to_string());
}
fingerprint.extension = derived_extension(&sets.sps[0], &summaries[0]);
Ok(AvcConfig { sets, fingerprint })
}
}
pub(crate) fn parse_avcc_parameter_sets(avcc: &[u8]) -> Result<AvcConfig, String> {
parse_avcc_record(avcc).map(|record| AvcConfig {
sets: record.sets,
fingerprint: record.fingerprint,
})
}
#[derive(Debug)]
struct AvccRecord {
header: CodecProjection,
extension: Option<(u8, u8, u8)>,
sets: ParameterSets,
fingerprint: ConfigFingerprint,
}
fn parse_avcc_record(avcc: &[u8]) -> Result<AvccRecord, String> {
if avcc.len() < 7 {
return Err(format!("avcC too short ({} bytes)", avcc.len()));
}
if avcc[0] != 1 {
return Err(format!("avcC configurationVersion is {} (expected 1)", avcc[0]));
}
let header = CodecProjection {
profile: avcc[1],
compatibility: avcc[2],
level: avcc[3],
};
if avcc[4] & 0xFC != 0xFC {
return Err(format!(
"avcC byte 4 reserved bits are cleared (0x{:02X}, expected 0xFC | lengthSizeMinusOne)",
avcc[4]
));
}
let length_size = (avcc[4] & 0x03) as usize + 1;
if length_size != NAL_LENGTH_SIZE {
return Err(format!(
"avcC NAL length size is {length_size} (the strict tier requires 4)"
));
}
if avcc[5] & 0xE0 != 0xE0 {
return Err(format!(
"avcC byte 5 reserved bits are cleared (0x{:02X}, expected 0xE0 | numOfSequenceParameterSets)",
avcc[5]
));
}
let sps_count = (avcc[5] & 0x1F) as usize;
let mut pos = 6usize;
let mut sets = ParameterSets {
sps: Vec::with_capacity(sps_count),
pps: Vec::new(),
};
for _ in 0..sps_count {
let ps = read_u16_prefixed(avcc, &mut pos).map_err(|e| format!("SPS entry: {e}"))?;
check_ps_nal_header(ps[0], NAL_SPS, "SPS")?;
sets.sps.push(ps);
}
if pos >= avcc.len() {
return Err("avcC truncated before the PPS count".to_string());
}
let pps_count = avcc[pos] as usize;
pos += 1;
for _ in 0..pps_count {
let ps = read_u16_prefixed(avcc, &mut pos).map_err(|e| format!("PPS entry: {e}"))?;
check_ps_nal_header(ps[0], NAL_PPS, "PPS")?;
sets.pps.push(ps);
}
if sets.sps.is_empty() || sets.pps.is_empty() {
return Err("avcC lacks an SPS or a PPS".to_string());
}
let mut fingerprint = ConfigFingerprint::default();
let mut summaries = Vec::with_capacity(sets.sps.len());
for sps in &sets.sps {
let summary = parse_sps(sps).map_err(|e| format!("avcC SPS: {e}"))?;
fingerprint.put_sps(summary.sps_id, sps);
summaries.push(summary);
}
for pps in &sets.pps {
let (pps_id, bound) = parse_pps(pps, &summaries).map_err(|e| format!("avcC PPS: {e}"))?;
fingerprint.put_pps(pps_id, pps, &sets.sps[bound]);
}
let (extension, sps_ext) = parse_avcc_extension(avcc, pos, header.profile)?;
fingerprint.extension = match extension {
Some(_) => derived_extension(&sets.sps[0], &summaries[0]),
None => None,
};
fingerprint.sps_ext = sps_ext;
let record = AvccRecord {
header,
extension,
sets,
fingerprint,
};
check_avcc_consistency(&record)?;
Ok(record)
}
fn check_ps_nal_header(header: u8, expected_type: u8, what: &str) -> Result<(), String> {
if header & 0x80 != 0 {
return Err(format!(
"avcC {what} entry NAL header 0x{header:02X} has forbidden_zero_bit set"
));
}
let nal_type = header & 0x1F;
if nal_type != expected_type {
return Err(format!(
"avcC {what} array entry carries NAL type {nal_type} (expected {expected_type})"
));
}
Ok(())
}
type AvccExtensionTail = (Option<(u8, u8, u8)>, Vec<Vec<u8>>);
fn parse_avcc_extension(
avcc: &[u8],
mut pos: usize,
profile: u8,
) -> Result<AvccExtensionTail, String> {
if pos == avcc.len() {
return Ok((None, Vec::new()));
}
let trailing = avcc.len() - pos;
if profile == 66 || profile == 77 || profile == 88 {
return Err(format!(
"avcC for profile {profile} carries {trailing} trailing byte(s) \
(no extension is defined)"
));
}
if trailing < 4 {
return Err(format!(
"avcC profile extension truncated ({trailing} byte(s); need the chroma \
format, two bit depths and the SPS-EXT count)"
));
}
if avcc[pos] & 0xFC != 0xFC {
return Err(format!(
"avcC extension chroma byte reserved bits are cleared (0x{:02X})",
avcc[pos]
));
}
let chroma_format_idc = avcc[pos] & 0x03;
if avcc[pos + 1] & 0xF8 != 0xF8 || avcc[pos + 2] & 0xF8 != 0xF8 {
return Err(format!(
"avcC extension bit-depth reserved bits are cleared (0x{:02X} 0x{:02X})",
avcc[pos + 1],
avcc[pos + 2]
));
}
let bit_depth_luma = (avcc[pos + 1] & 0x07) + 8;
let bit_depth_chroma = (avcc[pos + 2] & 0x07) + 8;
let sps_ext_count = avcc[pos + 3] as usize;
pos += 4;
let mut sps_ext = Vec::with_capacity(sps_ext_count);
for _ in 0..sps_ext_count {
let ps = read_u16_prefixed(avcc, &mut pos).map_err(|e| format!("SPS-EXT entry: {e}"))?;
check_ps_nal_header(ps[0], NAL_SPS_EXT, "SPS-EXT")?;
sps_ext.push(ps[1..].to_vec());
}
if pos != avcc.len() {
return Err(format!(
"avcC carries {} trailing byte(s) after the profile extension",
avcc.len() - pos
));
}
Ok((
Some((chroma_format_idc, bit_depth_luma, bit_depth_chroma)),
sps_ext,
))
}
fn check_avcc_consistency(record: &AvccRecord) -> Result<(), String> {
let derived = CodecProjection::from_ordered_sets(&record.sets)?;
if record.header != derived {
return Err(format!(
"avcC header declares profile/compatibility/level \
{:02X}{:02X}{:02X} but the first SPS carries {:02X}{:02X}{:02X}",
record.header.profile,
record.header.compatibility,
record.header.level,
derived.profile,
derived.compatibility,
derived.level
));
}
if let Some((chroma, luma, chroma_depth)) = record.extension {
let first_sps = record.sets.sps.first().ok_or("no SPS")?;
let summary = parse_sps(first_sps)?;
let writer_fields = writer_extension_triple(first_sps[1], &summary);
let syntax_fields = summary.chroma_info();
if (chroma, luma, chroma_depth) != writer_fields
&& (chroma, luma, chroma_depth) != syntax_fields
{
let (want_chroma, want_luma, want_chroma_depth) = writer_fields;
let mut accepted = format!("{want_chroma} and {want_luma}/{want_chroma_depth}");
if syntax_fields != writer_fields {
let (syn_chroma, syn_luma, syn_chroma_depth) = syntax_fields;
accepted = format!(
"{accepted} (writer default) or {syn_chroma} and \
{syn_luma}/{syn_chroma_depth} (SPS syntax)"
);
}
return Err(format!(
"avcC extension declares chroma_format_idc {chroma} and bit depths \
{luma}/{chroma_depth} but the first SPS derives {accepted}"
));
}
}
Ok(())
}
fn read_u16_prefixed(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, String> {
if data.len() - *pos < 2 {
return Err("truncated length".to_string());
}
let len = u16::from_be_bytes([data[*pos], data[*pos + 1]]) as usize;
*pos += 2;
if len == 0 {
return Err("zero-length parameter set".to_string());
}
if data.len() - *pos < len {
return Err("length overruns the record".to_string());
}
let out = data[*pos..*pos + len].to_vec();
*pos += len;
Ok(out)
}
fn writer_extension_triple(profile_idc: u8, summary: &SpsSummary) -> (u8, u8, u8) {
match profile_idc {
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 => {
summary.chroma_info()
}
_ => (1, 8, 8),
}
}
fn derived_extension(first_sps: &[u8], first_summary: &SpsSummary) -> Option<(u8, u8, u8)> {
match first_sps[1] {
66 | 77 | 88 => None,
profile => Some(writer_extension_triple(profile, first_summary)),
}
}
pub(crate) fn build_avcc(sets: &ParameterSets) -> Result<Vec<u8>, String> {
let first_sps = sets.sps.first().ok_or("no SPS")?;
if first_sps.len() < 4 {
return Err(format!("SPS too short ({} bytes)", first_sps.len()));
}
if sets.sps.len() > 0x1F || sets.pps.len() > 0xFF {
return Err("too many parameter sets for avcC".to_string());
}
let mut out = Vec::with_capacity(16 + first_sps.len());
out.push(1); out.push(first_sps[1]); out.push(first_sps[2]); out.push(first_sps[3]); out.push(0xFC | (NAL_LENGTH_SIZE as u8 - 1)); out.push(0xE0 | sets.sps.len() as u8);
for sps in &sets.sps {
if sps.len() > u16::MAX as usize {
return Err("SPS exceeds the 16-bit avcC length field".to_string());
}
out.extend_from_slice(&(sps.len() as u16).to_be_bytes());
out.extend_from_slice(sps);
}
out.push(sets.pps.len() as u8);
for pps in &sets.pps {
if pps.len() > u16::MAX as usize {
return Err("PPS exceeds the 16-bit avcC length field".to_string());
}
out.extend_from_slice(&(pps.len() as u16).to_be_bytes());
out.extend_from_slice(pps);
}
if let Some((chroma_format_idc, bit_depth_luma, bit_depth_chroma)) =
derived_extension(first_sps, &parse_sps(first_sps)?)
{
out.push(0xFC | (chroma_format_idc & 0x03));
out.push(0xF8 | ((bit_depth_luma - 8) & 0x07));
out.push(0xF8 | ((bit_depth_chroma - 8) & 0x07));
out.push(0); }
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CodecProjection {
pub(crate) profile: u8,
pub(crate) compatibility: u8,
pub(crate) level: u8,
}
impl CodecProjection {
fn from_ordered_sets(sets: &ParameterSets) -> Result<Self, String> {
let first_sps = sets.sps.first().ok_or("no SPS")?;
if first_sps.len() < 4 {
return Err(format!("SPS too short ({} bytes)", first_sps.len()));
}
Ok(Self {
profile: first_sps[1],
compatibility: first_sps[2],
level: first_sps[3],
})
}
pub(crate) fn codec_string(&self) -> String {
format!(
"avc1.{:02X}{:02X}{:02X}",
self.profile, self.compatibility, self.level
)
}
}
fn unescape_rbsp(payload: &[u8]) -> Vec<u8> {
let mut rbsp = Vec::with_capacity(payload.len());
let mut zeros = 0u32;
for &b in payload {
if zeros >= 2 && b == 3 {
zeros = 0;
continue;
}
if b == 0 {
zeros += 1;
} else {
zeros = 0;
}
rbsp.push(b);
}
rbsp
}
#[derive(Debug, Clone, Copy)]
struct SpsSummary {
sps_id: u32,
chroma_format_idc: u8,
bit_depth_luma: u8,
bit_depth_chroma: u8,
pic_width_in_mbs: u32,
pic_height_in_map_units: u32,
}
impl SpsSummary {
fn chroma_info(&self) -> (u8, u8, u8) {
(self.chroma_format_idc, self.bit_depth_luma, self.bit_depth_chroma)
}
}
fn parse_sps(sps: &[u8]) -> Result<SpsSummary, String> {
let rbsp = unescape_rbsp(&sps[1..]);
let mut r = BitReader::new(&rbsp);
let profile_idc = r.bits(8)? as u8;
r.bits(6)?; let reserved_zero_2bits = r.bits(2)?;
if reserved_zero_2bits != 0 {
return Err(format!(
"reserved_zero_2bits is {reserved_zero_2bits} (7.4.2.1.1 requires 0)"
));
}
r.bits(8)?; let sps_id = r.ue()?;
if sps_id > 31 {
return Err(format!("seq_parameter_set_id {sps_id} exceeds 31"));
}
let chroma_info = match profile_idc {
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135 | 144 => {
let chroma_format_idc = r.ue()?;
if chroma_format_idc > 3 {
return Err(format!("invalid chroma_format_idc {chroma_format_idc}"));
}
if chroma_format_idc == 3 {
r.bits(1)?; }
let bit_depth_luma_minus8 = r.ue()?;
let bit_depth_chroma_minus8 = r.ue()?;
if bit_depth_luma_minus8 > 6 || bit_depth_chroma_minus8 > 6 {
return Err(format!(
"invalid SPS bit depth (bit_depth_luma_minus8 {bit_depth_luma_minus8}, \
bit_depth_chroma_minus8 {bit_depth_chroma_minus8}; both must be <= 6)"
));
}
r.bits(1)?; if r.bits(1)? == 1 {
let lists = if chroma_format_idc != 3 { 8 } else { 12 };
for i in 0..lists {
if r.bits(1)? == 1 {
skip_scaling_list(&mut r, if i < 6 { 16 } else { 64 })?;
}
}
}
(
chroma_format_idc as u8,
bit_depth_luma_minus8 as u8 + 8,
bit_depth_chroma_minus8 as u8 + 8,
)
}
_ => (1, 8, 8),
};
let (chroma_format_idc, bit_depth_luma, bit_depth_chroma) = chroma_info;
let log2_max_frame_num_minus4 = r.ue()?;
if log2_max_frame_num_minus4 > 12 {
return Err(format!(
"log2_max_frame_num_minus4 {log2_max_frame_num_minus4} out of range (0..=12)"
));
}
let pic_order_cnt_type = r.ue()?;
match pic_order_cnt_type {
0 => {
let log2_max_poc_lsb_minus4 = r.ue()?;
if log2_max_poc_lsb_minus4 > 12 {
return Err(format!(
"log2_max_pic_order_cnt_lsb_minus4 {log2_max_poc_lsb_minus4} \
out of range (0..=12)"
));
}
}
1 => {
r.bits(1)?; r.se()?; r.se()?; let cycle_len = r.ue()?;
if cycle_len >= 256 {
return Err(format!(
"num_ref_frames_in_pic_order_cnt_cycle {cycle_len} exceeds 255"
));
}
for _ in 0..cycle_len {
r.se()?; }
}
2 => {}
other => return Err(format!("pic_order_cnt_type {other} (must be <= 2)")),
}
let max_num_ref_frames = r.ue()?;
if max_num_ref_frames > 16 {
return Err(format!(
"max_num_ref_frames {max_num_ref_frames} exceeds 16"
));
}
r.bits(1)?; let pic_width_in_mbs = r.ue()? as u64 + 1; let pic_height_in_map_units = r.ue()? as u64 + 1; let frame_mbs_only = r.bits(1)? == 1;
if !frame_mbs_only {
r.bits(1)?; }
let width = 16 * pic_width_in_mbs;
let height = 16 * pic_height_in_map_units * if frame_mbs_only { 1 } else { 2 };
if width > 65535 || height > 65535 {
return Err(format!(
"coded picture size {width}x{height} exceeds 65535 on an axis"
));
}
if (8 * width + 1024) * (height + 128) >= 1 << 31 {
return Err(format!(
"coded picture size {width}x{height} overflows the sample buffer bound"
));
}
r.bits(1)?; if r.bits(1)? == 1 {
let crop_left = r.ue()? as u64;
let crop_right = r.ue()? as u64;
let crop_top = r.ue()? as u64;
let crop_bottom = r.ue()? as u64;
let step_x: u64 = if chroma_format_idc == 1 || chroma_format_idc == 2 { 2 } else { 1 };
let step_y: u64 = (if chroma_format_idc == 1 { 2 } else { 1 })
* (if frame_mbs_only { 1 } else { 2 });
if (crop_left + crop_right) * step_x >= width
|| (crop_top + crop_bottom) * step_y >= height
{
return Err(format!(
"frame cropping {crop_left}/{crop_right}/{crop_top}/{crop_bottom} \
removes the whole {width}x{height} picture"
));
}
}
if r.bits(1)? == 1 {
skip_vui(&mut r, max_num_ref_frames)?;
}
r.finish_rbsp()?;
Ok(SpsSummary {
sps_id,
chroma_format_idc,
bit_depth_luma,
bit_depth_chroma,
pic_width_in_mbs: pic_width_in_mbs as u32,
pic_height_in_map_units: pic_height_in_map_units as u32,
})
}
fn skip_scaling_list(r: &mut BitReader, size: u32) -> Result<(), String> {
let mut last_scale: i64 = 8;
let mut next_scale: i64 = 8;
for _ in 0..size {
if next_scale != 0 {
let delta_scale = r.se()?;
if !(-128..=127).contains(&delta_scale) {
return Err(format!("delta_scale {delta_scale} outside [-128, 127]"));
}
next_scale = (last_scale + delta_scale + 256).rem_euclid(256);
}
if next_scale != 0 {
last_scale = next_scale;
}
}
Ok(())
}
fn parse_pps(pps: &[u8], sps_context: &[SpsSummary]) -> Result<(u32, usize), String> {
let rbsp = unescape_rbsp(&pps[1..]);
let mut r = BitReader::new(&rbsp);
let pps_id = r.ue()?;
if pps_id > 255 {
return Err(format!("pic_parameter_set_id {pps_id} exceeds 255"));
}
let sps_id = r.ue()?;
if sps_id > 31 {
return Err(format!("seq_parameter_set_id {sps_id} exceeds 31"));
}
let (bound_index, sps) = sps_context
.iter()
.enumerate()
.rev()
.find(|(_, s)| s.sps_id == sps_id)
.ok_or_else(|| {
format!("references seq_parameter_set_id {sps_id}, which no preceding SPS in the configuration carries")
})?;
r.bits(1)?; r.bits(1)?; let num_slice_groups_minus1 = r.ue()?;
if num_slice_groups_minus1 > 7 {
return Err(format!(
"num_slice_groups_minus1 {num_slice_groups_minus1} exceeds 7"
));
}
if num_slice_groups_minus1 > 0 {
check_slice_group_map(&mut r, num_slice_groups_minus1, sps)?;
}
let num_ref_idx_l0 = r.ue()?;
let num_ref_idx_l1 = r.ue()?;
if num_ref_idx_l0 > 31 || num_ref_idx_l1 > 31 {
return Err(format!(
"num_ref_idx_l0/l1_default_active_minus1 {num_ref_idx_l0}/{num_ref_idx_l1} \
exceeds 31"
));
}
r.bits(1)?; let weighted_bipred_idc = r.bits(2)?;
if weighted_bipred_idc > 2 {
return Err(format!(
"weighted_bipred_idc {weighted_bipred_idc} (must be <= 2)"
));
}
let pic_init_qp_minus26 = r.se()?;
let qp_floor = -(26 + 6 * (sps.bit_depth_luma as i64 - 8));
if !(qp_floor..=25).contains(&pic_init_qp_minus26) {
return Err(format!(
"pic_init_qp_minus26 {pic_init_qp_minus26} outside [{qp_floor}, 25] \
(the referenced SPS codes {}-bit luma)",
sps.bit_depth_luma
));
}
let pic_init_qs_minus26 = r.se()?;
if !(-26..=25).contains(&pic_init_qs_minus26) {
return Err(format!(
"pic_init_qs_minus26 {pic_init_qs_minus26} outside [-26, 25]"
));
}
let chroma_qp_index_offset = r.se()?;
if !(-12..=12).contains(&chroma_qp_index_offset) {
return Err(format!(
"chroma_qp_index_offset {chroma_qp_index_offset} outside [-12, 12]"
));
}
r.bits(1)?; r.bits(1)?; r.bits(1)?; if r.more_rbsp_data() {
let transform_8x8_mode_flag = r.bits(1)? == 1;
if r.bits(1)? == 1 {
let lists = 6
+ if transform_8x8_mode_flag {
if sps.chroma_format_idc != 3 {
2
} else {
6
}
} else {
0
};
for i in 0..lists {
if r.bits(1)? == 1 {
skip_scaling_list(&mut r, if i < 6 { 16 } else { 64 })?;
}
}
}
let second_chroma_qp_index_offset = r.se()?;
if !(-12..=12).contains(&second_chroma_qp_index_offset) {
return Err(format!(
"second_chroma_qp_index_offset {second_chroma_qp_index_offset} \
outside [-12, 12]"
));
}
}
r.finish_rbsp()?;
Ok((pps_id, bound_index))
}
fn check_slice_group_map(
r: &mut BitReader,
num_slice_groups_minus1: u32,
sps: &SpsSummary,
) -> Result<(), String> {
let pic_size_in_map_units =
sps.pic_width_in_mbs as u64 * sps.pic_height_in_map_units as u64;
let map_type = r.ue()?;
match map_type {
0 => {
for _ in 0..=num_slice_groups_minus1 {
let run_length_minus1 = r.ue()?;
if run_length_minus1 as u64 >= pic_size_in_map_units {
return Err(format!(
"slice-group run_length_minus1 {run_length_minus1} reaches past \
the {pic_size_in_map_units} map units of the referenced SPS"
));
}
}
}
1 => {}
2 => {
for _ in 0..num_slice_groups_minus1 {
let top_left = r.ue()?;
let bottom_right = r.ue()?;
if bottom_right as u64 >= pic_size_in_map_units {
return Err(format!(
"slice-group bottom_right {bottom_right} is outside the \
{pic_size_in_map_units} map units of the referenced SPS"
));
}
let width = sps.pic_width_in_mbs;
if top_left > bottom_right || top_left % width > bottom_right % width {
return Err(format!(
"slice-group rectangle {top_left}..{bottom_right} is inverted \
(7.4.2.2 orders both corners, by map unit and by column)"
));
}
}
}
3..=5 => {
if num_slice_groups_minus1 != 1 {
return Err(format!(
"slice_group_map_type {map_type} requires exactly two slice groups \
(num_slice_groups_minus1 is {num_slice_groups_minus1})"
));
}
r.bits(1)?; let change_rate_minus1 = r.ue()?;
if change_rate_minus1 as u64 >= pic_size_in_map_units {
return Err(format!(
"slice_group_change_rate_minus1 {change_rate_minus1} reaches past \
the {pic_size_in_map_units} map units of the referenced SPS"
));
}
}
6 => {
let pic_size_in_map_units_minus1 = r.ue()?;
if pic_size_in_map_units_minus1 as u64 + 1 != pic_size_in_map_units {
return Err(format!(
"slice-group table declares {} map units but the referenced SPS \
codes {pic_size_in_map_units}",
pic_size_in_map_units_minus1 as u64 + 1
));
}
let width = 32 - num_slice_groups_minus1.leading_zeros();
for _ in 0..=pic_size_in_map_units_minus1 {
let slice_group_id = r.bits(width)?;
if slice_group_id > num_slice_groups_minus1 {
return Err(format!(
"slice_group_id {slice_group_id} exceeds \
num_slice_groups_minus1 {num_slice_groups_minus1}"
));
}
}
}
other => return Err(format!("slice_group_map_type {other} (must be <= 6)")),
}
Ok(())
}
fn skip_vui(r: &mut BitReader, max_num_ref_frames: u32) -> Result<(), String> {
if r.bits(1)? == 1 {
if r.bits(8)? == 255 {
r.bits(16)?; r.bits(16)?; }
}
if r.bits(1)? == 1 {
r.bits(1)?; }
if r.bits(1)? == 1 {
r.bits(3)?; r.bits(1)?; if r.bits(1)? == 1 {
r.bits(24)?;
}
}
if r.bits(1)? == 1 {
let top = r.ue()?; let bottom = r.ue()?; if top > 5 || bottom > 5 {
return Err(format!(
"chroma_sample_loc_type {top}/{bottom} exceeds 5"
));
}
}
if r.bits(1)? == 1 {
let num_units_in_tick = r.bits(32)?;
let time_scale = r.bits(32)?;
if num_units_in_tick == 0 || time_scale == 0 {
return Err(format!(
"timing_info {num_units_in_tick}/{time_scale} carries a zero field"
));
}
r.bits(1)?; }
let nal_hrd = r.bits(1)? == 1; if nal_hrd {
skip_hrd_parameters(r)?;
}
let vcl_hrd = r.bits(1)? == 1; if vcl_hrd {
skip_hrd_parameters(r)?;
}
if nal_hrd || vcl_hrd {
r.bits(1)?; }
r.bits(1)?; if r.bits(1)? == 1 {
r.bits(1)?; let max_bytes_per_pic_denom = r.ue()?;
let max_bits_per_mb_denom = r.ue()?;
let log2_max_mv_length_horizontal = r.ue()?;
let log2_max_mv_length_vertical = r.ue()?;
let max_num_reorder_frames = r.ue()?;
let max_dec_frame_buffering = r.ue()?;
if max_bytes_per_pic_denom > 16 {
return Err(format!(
"max_bytes_per_pic_denom {max_bytes_per_pic_denom} exceeds 16"
));
}
if max_bits_per_mb_denom > 16 {
return Err(format!(
"max_bits_per_mb_denom {max_bits_per_mb_denom} exceeds 16"
));
}
if log2_max_mv_length_horizontal > 16 || log2_max_mv_length_vertical > 16 {
return Err(format!(
"log2_max_mv_length {log2_max_mv_length_horizontal}/\
{log2_max_mv_length_vertical} exceeds 16"
));
}
if max_num_reorder_frames > 16 || max_num_reorder_frames > max_dec_frame_buffering {
return Err(format!(
"max_num_reorder_frames {max_num_reorder_frames} exceeds 16 or \
max_dec_frame_buffering {max_dec_frame_buffering}"
));
}
if max_dec_frame_buffering > 16 || max_dec_frame_buffering < max_num_ref_frames {
return Err(format!(
"max_dec_frame_buffering {max_dec_frame_buffering} is outside \
max_num_ref_frames {max_num_ref_frames}..=16"
));
}
}
Ok(())
}
fn skip_hrd_parameters(r: &mut BitReader) -> Result<(), String> {
let cpb_cnt_minus1 = r.ue()?;
if cpb_cnt_minus1 > 31 {
return Err(format!("cpb_cnt_minus1 {cpb_cnt_minus1} exceeds 31"));
}
r.bits(4)?; r.bits(4)?; for _ in 0..=cpb_cnt_minus1 {
r.ue()?; r.ue()?; r.bits(1)?; }
r.bits(5)?; r.bits(5)?; r.bits(5)?; r.bits(5)?; Ok(())
}
struct BitReader<'a> {
data: &'a [u8],
pos: usize, }
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn bits(&mut self, n: u32) -> Result<u32, String> {
let mut v = 0u32;
for _ in 0..n {
let byte = self
.data
.get(self.pos / 8)
.ok_or_else(|| "RBSP truncated".to_string())?;
let bit = (byte >> (7 - (self.pos % 8))) & 1;
v = (v << 1) | bit as u32;
self.pos += 1;
}
Ok(v)
}
fn more_rbsp_data(&self) -> bool {
for (i, &b) in self.data.iter().enumerate().rev() {
if b != 0 {
let last_set = i * 8 + 7 - b.trailing_zeros() as usize;
return self.pos < last_set;
}
}
false
}
fn ue(&mut self) -> Result<u32, String> {
let mut zeros = 0u32;
while self.bits(1)? == 0 {
zeros += 1;
if zeros > 31 {
return Err("invalid Exp-Golomb code".to_string());
}
}
if zeros == 0 {
return Ok(0);
}
let rest = self.bits(zeros)?;
Ok((1u32 << zeros) - 1 + rest)
}
fn se(&mut self) -> Result<i64, String> {
let k = self.ue()? as i64;
Ok(if k % 2 == 1 { (k + 1) / 2 } else { -(k / 2) })
}
fn finish_rbsp(&mut self) -> Result<(), String> {
if self.bits(1)? != 1 {
return Err("rbsp_trailing_bits stop bit is 0".to_string());
}
#[allow(clippy::manual_is_multiple_of)]
while self.pos % 8 != 0 {
if self.bits(1)? != 0 {
return Err("nonzero rbsp_trailing_bits alignment bit".to_string());
}
}
let left = self.data.len() - self.pos / 8;
if left != 0 {
return Err(format!("{left} byte(s) after rbsp_trailing_bits"));
}
Ok(())
}
}
pub(crate) struct AvcRuntime {
annexb_packets: bool,
baseline: ConfigFingerprint,
projection: CodecProjection,
}
impl AvcRuntime {
pub(crate) fn from_extradata(
extradata: &[u8],
stream_index: usize,
) -> Result<(Self, Vec<u8>, CodecProjection), PacketSinkError> {
let invalid = |reason: String| PacketSinkError::InvalidExtradata {
stream_index,
reason,
};
let annexb_packets = extradata.first() != Some(&1);
let config = parse_parameter_sets(extradata).map_err(&invalid)?;
let projection = CodecProjection::from_ordered_sets(&config.sets).map_err(&invalid)?;
let delivered = if annexb_packets {
let record = build_avcc(&config.sets).map_err(&invalid)?;
let reparsed = parse_avcc_parameter_sets(&record).map_err(|reason| {
invalid(format!(
"the parameter-set interleaving has no faithful avcC \
representation (the synthesized record fails to \
reparse: {reason})"
))
})?;
if reparsed.fingerprint != config.fingerprint {
return Err(invalid(
"the parameter-set interleaving has no faithful avcC \
representation (reparsing the synthesized record binds \
a PPS to a different SPS generation)"
.to_string(),
));
}
record
} else {
extradata.to_vec()
};
Ok((
Self {
annexb_packets,
baseline: config.fingerprint,
projection,
},
delivered,
projection,
))
}
pub(crate) fn check_new_extradata(
&self,
bytes: &[u8],
stream_index: usize,
) -> Result<(), PacketSinkError> {
let config = parse_parameter_sets(bytes).map_err(|reason| {
PacketSinkError::ConfigChange {
stream_index,
what: format!("invalid NEW_EXTRADATA ({reason})"),
}
})?;
let projection = CodecProjection::from_ordered_sets(&config.sets).map_err(|reason| {
PacketSinkError::ConfigChange {
stream_index,
what: format!("invalid NEW_EXTRADATA SPS ({reason})"),
}
})?;
if projection != self.projection {
return Err(PacketSinkError::ConfigChange {
stream_index,
what: format!(
"derived codec projection changed ({} -> {})",
self.projection.codec_string(),
projection.codec_string()
),
});
}
if config.fingerprint != self.baseline {
return Err(PacketSinkError::ConfigChange {
stream_index,
what: "NEW_EXTRADATA changes the effective parameter sets".to_string(),
});
}
Ok(())
}
pub(crate) fn normalize_au<'a>(
&self,
payload: &'a [u8],
scratch: &'a mut Vec<u8>,
stream_index: usize,
) -> Result<(bool, &'a [u8]), PacketSinkError> {
let malformed = |reason: String| PacketSinkError::MalformedPacket {
stream_index,
reason,
};
let (scan, data): (_, &'a [u8]) = if self.annexb_packets {
scratch.clear();
let mut exact = 0usize;
walk_annexb(payload, |nal| exact += NAL_LENGTH_SIZE + nal.len())
.map_err(&malformed)?;
scratch.reserve(exact);
let scan = walk_annexb(payload, |nal| push_length_prefixed(nal, scratch))
.map_err(&malformed)?;
(scan, scratch.as_slice())
} else {
let scan = walk_length_prefixed(payload, |_| {}).map_err(malformed)?;
(scan, payload)
};
if scan.has_parameter_set {
self.check_inband_parameter_sets(data, stream_index)?;
return Err(PacketSinkError::InBandParameterSets { stream_index });
}
Ok((scan.has_idr, data))
}
fn check_inband_parameter_sets(
&self,
data: &[u8],
stream_index: usize,
) -> Result<(), PacketSinkError> {
let mut mismatch = false;
let _ = walk_length_prefixed(data, |nal| {
let matches_baseline = match nal[0] & 0x1F {
NAL_SPS => self.baseline.has_active_sps(nal),
NAL_PPS => self.baseline.has_active_pps(nal),
_ => return,
};
if !matches_baseline {
mismatch = true;
}
});
if mismatch {
return Err(PacketSinkError::ConfigChange {
stream_index,
what: "in-band SPS/PPS differ from the stream configuration".to_string(),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::super::super::nal_framing::collect_annexb;
use super::*;
const SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD9, 0x01, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10,
0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xE4, 0x80,
];
const PPS: &[u8] = &[0x68, 0xCB, 0x83, 0xCB, 0x20];
const SAME_PROJ_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD9, 0x00, 0xA0, 0x3D, 0xB0, 0x11, 0x00, 0x00, 0x03, 0x00,
0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0F, 0x16, 0x2E, 0x48,
];
const HIGH_SPS: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x41, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00,
0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xD9, 0x60,
];
const HIGH_PPS: &[u8] = &[0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0];
const HIGH_SPS_SCALING: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAD, 0x00, 0xD9, 0x41, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00,
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xD9, 0x60,
];
const PROFILE_144_SPS: &[u8] = &[
0x67, 0x90, 0x00, 0x1E, 0xAC, 0xD9, 0x41, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xD9, 0x60,
];
const PROFILE_144_CHROMA3_SPS: &[u8] = &[
0x67, 0x90, 0x00, 0x1E, 0x91, 0xB0, 0x88, 0x00, 0xB4, 0x0A, 0x0F, 0xC8,
];
const PROFILE_135_TEN_BIT_SPS: &[u8] = &[
0x67, 0x87, 0x00, 0x1E, 0xA6, 0xCB, 0x40, 0xA0, 0xFC, 0x80,
];
const SCALING_LIST_SPS: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAD, 0xFF, 0xFF, 0x80, 0xE8, 0x14, 0x1F, 0x90,
];
const CHROMA3_SCALING_SPS: &[u8] = &[
0x67, 0x6E, 0x00, 0x1E, 0x91, 0xB0, 0x88, 0x00, 0xB4, 0x0A, 0x0F, 0xC8,
];
const SPS_ID_31: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0x04, 0x16, 0x81, 0x41, 0xF9];
const SPS_ID_32: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0x04, 0x36, 0x81, 0x41, 0xF9];
const LOG2_FRAME_NUM_12: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0x8D, 0x68, 0x14, 0x1F, 0x90];
const LOG2_FRAME_NUM_13: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0x8E, 0x68, 0x14, 0x1F, 0x90];
const POC_TYPE_3: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xC8, 0x81, 0x41, 0xF9];
const POC_CYCLE_255: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD3, 0x00, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0xA0, 0xFC,
0x80,
];
const POC_CYCLE_256: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD3, 0x00, 0x80, 0xA0, 0x50, 0x7E, 0x40,
];
const MISSING_CROP_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xF8];
const VUI_TRUNCATED_MIN_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8];
const BAD_STOP_BIT_SPS: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x41, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xD9, 0x40,
];
const REF_FRAMES_16_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xD8, 0x44, 0x14, 0x1F, 0x90];
const REF_FRAMES_17_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xD8, 0x48, 0x14, 0x1F, 0x90];
const WIDTH_65520_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x00, 0x0F, 0xFF, 0xE4];
const WIDTH_65536_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x00, 0x04, 0x00, 0x39];
const PIXEL_PRODUCT_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x00, 0x0F, 0xFF, 0x00, 0x1F, 0xFF, 0x90,
];
const NEAR_FULL_CROP_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xF0, 0x28, 0x01, 0x47, 0x40,
];
const FULL_CROP_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xF0, 0x28, 0x81, 0x47, 0x40,
];
const REORDER_16_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x06, 0x11, 0x08, 0x84, 0x42, 0x21,
0x10, 0x8C,
];
const REORDER_17_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x07, 0xE1, 0x20, 0x94,
];
const REORDER_ABOVE_BUFFERING_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x07, 0xED, 0x40,
];
const BYTES_DENOM_17_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x06, 0x12, 0xFC,
];
const MB_DENOM_17_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x07, 0x09, 0x7C,
];
const MV_LEN_17_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x07, 0x84, 0xBC,
];
const BUFFERING_17_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x05, 0xF0, 0x94,
];
const BUFFERING_BELOW_REF_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x05, 0xFC,
];
const REF_EQUALS_BUFFERING_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD8, 0x44, 0x14, 0x1F, 0xA0, 0x17, 0xC2, 0x30,
];
const ZERO_TIME_SCALE_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40,
0x00, 0x00, 0x03, 0x00, 0x01,
];
const ZERO_NUM_UNITS_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00,
0x03, 0x00, 0x00, 0x06, 0x41,
];
const CHROMA_LOC_5_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x98, 0xC0, 0x80,
];
const CHROMA_LOC_6_SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x05, 0x07, 0xE8, 0x9E, 0x08,
];
const TEN_BIT_SPS: &[u8] = &[
0x67, 0x6E, 0x00, 0x1E, 0xA6, 0xCB, 0x40, 0xA0, 0xFC, 0x80,
];
const DELTA_SCALE_M128_SPS: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAD, 0x80, 0x40, 0x7F, 0xFF, 0x80, 0xB4, 0x0A, 0x0F, 0xC8,
];
const DELTA_SCALE_128_SPS: &[u8] = &[
0x67, 0x64, 0x00, 0x1E, 0xAD, 0x80, 0x40, 0x3F, 0xFF, 0x80, 0xB4, 0x0A, 0x0F, 0xC8,
];
const MAP_UNITS_4_SPS: &[u8] = &[0x67, 0x42, 0xC0, 0x1E, 0xDA, 0x25, 0x90];
const MINIMAL_PPS: &[u8] = &[0x68, 0xCE, 0x38, 0x80];
const PPS_ID_255_PPS: &[u8] = &[0x68, 0x00, 0x80, 0x4E, 0x38, 0x80];
const PPS_ID_256_PPS: &[u8] = &[0x68, 0x00, 0x80, 0xCE, 0x38, 0x80];
const SPS_ID_31_PPS: &[u8] = &[0x68, 0x82, 0x03, 0x8E, 0x20];
const SPS_ID_32_PPS: &[u8] = &[0x68, 0x82, 0x13, 0x8E, 0x20];
const WEIGHTED_BIPRED_2_PPS: &[u8] = &[0x68, 0xCE, 0xB8, 0x80];
const WEIGHTED_BIPRED_3_PPS: &[u8] = &[0x68, 0xCE, 0xF8, 0x80];
const CHROMA_QP_12_PPS: &[u8] = &[0x68, 0xCE, 0x30, 0xC0, 0x80];
const CHROMA_QP_13_PPS: &[u8] = &[0x68, 0xCE, 0x30, 0xD0, 0x80];
const INIT_QP_M26_PPS: &[u8] = &[0x68, 0xCE, 0x01, 0xAE, 0x20];
const INIT_QP_M27_PPS: &[u8] = &[0x68, 0xCE, 0x01, 0xBE, 0x20];
const INIT_QP_M32_PPS: &[u8] = &[0x68, 0xCE, 0x00, 0x83, 0x88];
const INIT_QP_M88_PPS: &[u8] = &[0x68, 0xCE, 0x00, 0x58, 0xE2];
const INIT_QP_M89_PPS: &[u8] = &[0x68, 0xCE, 0x00, 0x59, 0xE2];
const INIT_QS_M27_PPS: &[u8] = &[0x68, 0xCE, 0x20, 0xDE, 0x20];
const REF_IDX_L0_32_PPS: &[u8] = &[0x68, 0xC8, 0x21, 0x8E, 0x20];
const SCALING_TAIL_8_PPS: &[u8] = &[0x68, 0xCE, 0x38, 0xC0, 0x30];
const SCALING_TAIL_12_PPS: &[u8] = &[0x68, 0xCE, 0x38, 0xC0, 0x03];
const FMO_TYPE0_PPS: &[u8] = &[0x68, 0xC5, 0xF1, 0xC4];
const FMO_TYPE2_PPS: &[u8] = &[0x68, 0xC4, 0xFC, 0x71];
const FMO_TYPE3_PPS: &[u8] = &[0x68, 0xC4, 0x47, 0x1C, 0x40];
const FMO_TYPE6_PPS: &[u8] = &[0x68, 0xC4, 0x72, 0x2E, 0x38, 0x80];
const FMO_GROUPS_8_PPS: &[u8] = &[0x68, 0xC1, 0x30];
const FMO_TYPE7_PPS: &[u8] = &[0x68, 0xC4, 0x22];
const FMO_TYPE0_RUN_300_PPS: &[u8] = &[0x68, 0xC5, 0x00, 0x96, 0xF1, 0xC4];
const FMO_TYPE2_BR_300_PPS: &[u8] = &[0x68, 0xC4, 0xE0, 0x12, 0xDC, 0x71];
const FMO_RATE_300_PPS: &[u8] = &[0x68, 0xC4, 0x40, 0x04, 0xB7, 0x1C, 0x40];
const FMO_TYPE6_SIZE_300_PPS: &[u8] = &[0x68, 0xC4, 0x70, 0x09, 0x6B, 0x1C, 0x40];
const FMO_TYPE2_INVERTED_PPS: &[u8] = &[0x68, 0xC4, 0xC1, 0x48, 0x57, 0x1C, 0x40];
const FMO_TYPE2_COLUMN_PPS: &[u8] = &[0x68, 0xC4, 0xD0, 0x57, 0x1C, 0x40];
const FMO_TYPE3_GROUPS_3_PPS: &[u8] = &[0x68, 0xC6, 0x47, 0x1C, 0x40];
const FMO_TYPE6_ID_3_PPS: &[u8] = &[0x68, 0xC6, 0x72, 0x0D, 0xE3, 0x88];
const BAD_STOP_BIT_PPS: &[u8] = &[0x68, 0xCB, 0x83, 0xCB, 0x00];
fn annexb_concat(nals: &[&[u8]]) -> Vec<u8> {
let mut v = Vec::new();
for (i, nal) in nals.iter().enumerate() {
v.extend_from_slice(if i == 0 { &[0, 0, 0, 1][..] } else { &[0, 0, 1][..] });
v.extend_from_slice(nal);
}
v
}
fn annexb_with(sps: &[u8], pps: &[u8]) -> Vec<u8> {
annexb_concat(&[sps, pps])
}
fn annexb_config() -> Vec<u8> {
annexb_with(SPS, PPS)
}
fn sps_ctx(sps_list: &[&[u8]]) -> Vec<SpsSummary> {
sps_list.iter().map(|s| parse_sps(s).unwrap()).collect()
}
#[test]
fn builds_and_reparses_avcc() {
let sets = parse_parameter_sets(&annexb_config()).unwrap().sets;
assert_eq!(sets.sps, vec![SPS.to_vec()]);
assert_eq!(sets.pps, vec![PPS.to_vec()]);
let avcc = build_avcc(&sets).unwrap();
assert_eq!(avcc[0], 1);
assert_eq!(avcc[1], 66);
assert_eq!(avcc[4] & 0x03, 3, "lengthSizeMinusOne must be 3");
assert_eq!(avcc[5] & 0x1F, 1);
let reparsed = parse_avcc_parameter_sets(&avcc).unwrap().sets;
assert_eq!(reparsed, sets);
let sps_len = SPS.len();
let pps_len = PPS.len();
assert_eq!(avcc.len(), 6 + 2 + sps_len + 1 + 2 + pps_len);
let projection = CodecProjection::from_ordered_sets(&sets).unwrap();
assert_eq!(projection.codec_string(), "avc1.42C01E");
assert_eq!(
(projection.profile, projection.compatibility, projection.level),
(66, 0xC0, 0x1E)
);
}
#[test]
fn high_profile_avcc_carries_the_extension() {
let sets = ParameterSets {
sps: vec![HIGH_SPS.to_vec()],
pps: vec![HIGH_PPS.to_vec()],
};
let avcc = build_avcc(&sets).unwrap();
let tail = &avcc[avcc.len() - 4..];
assert_eq!(tail[0], 0xFC | 1, "chroma_format_idc");
assert_eq!(tail[1], 0xF8, "bit_depth_luma_minus8");
assert_eq!(tail[2], 0xF8, "bit_depth_chroma_minus8");
assert_eq!(tail[3], 0, "numOfSequenceParameterSetExt");
let projection = CodecProjection::from_ordered_sets(&sets).unwrap();
assert_eq!(projection.codec_string(), "avc1.64001E");
}
#[test]
fn rejects_non_four_byte_avcc() {
let sets = parse_parameter_sets(&annexb_config()).unwrap().sets;
let mut avcc = build_avcc(&sets).unwrap();
avcc[4] = 0xFC | 1; assert!(parse_avcc_parameter_sets(&avcc).is_err());
}
#[test]
fn fingerprint_is_wrapper_independent() {
let from_annexb = parse_parameter_sets(&annexb_config()).unwrap();
let avcc = build_avcc(&from_annexb.sets).unwrap();
let from_avcc = parse_parameter_sets(&avcc).unwrap();
assert_eq!(from_annexb.sets, from_avcc.sets);
assert_eq!(from_annexb.fingerprint, from_avcc.fingerprint);
}
#[test]
fn fingerprint_keys_parameter_sets_by_identity() {
let fp = |config: &[u8]| parse_parameter_sets(config).unwrap().fingerprint;
let a = annexb_concat(&[SPS, SPS_ID_31, PPS]);
let b = annexb_concat(&[SPS_ID_31, SPS, PPS]);
assert_eq!(fp(&a), fp(&b), "distinct-id order must not matter");
let dup = annexb_concat(&[SPS, SPS, PPS, PPS]);
let single = annexb_concat(&[SPS, PPS]);
assert_eq!(fp(&dup), fp(&single), "identical resend is redundant");
let ab = annexb_concat(&[SPS, SAME_PROJ_SPS, PPS]);
let ba = annexb_concat(&[SAME_PROJ_SPS, SPS, PPS]);
assert_ne!(fp(&ab), fp(&ba), "same-id reorder swaps the active SPS");
let pab = annexb_concat(&[SPS, PPS, MINIMAL_PPS]);
let pba = annexb_concat(&[SPS, MINIMAL_PPS, PPS]);
assert_ne!(fp(&pab), fp(&pba), "same-id reorder swaps the active PPS");
}
#[test]
fn runtime_normalizes_annexb_and_passes_through_avcc() {
let (runtime, delivered, projection) =
AvcRuntime::from_extradata(&annexb_config(), 0).unwrap();
assert_eq!(projection.codec_string(), "avc1.42C01E");
assert_eq!(delivered[0], 1);
let mut scratch = Vec::new();
let au = vec![0, 0, 0, 1, 0x65, 0x88, 0x80];
let (is_key, data) = runtime.normalize_au(&au, &mut scratch, 0).unwrap();
assert!(is_key);
assert_eq!(data, &[0, 0, 0, 3, 0x65, 0x88, 0x80]);
let avcc = build_avcc(&parse_parameter_sets(&annexb_config()).unwrap().sets).unwrap();
let (runtime, _, _) = AvcRuntime::from_extradata(&avcc, 0).unwrap();
let lp = vec![0, 0, 0, 2, 0x41, 0x9A];
let mut scratch = Vec::new();
let (is_key, data) = runtime.normalize_au(&lp, &mut scratch, 0).unwrap();
assert!(!is_key);
assert_eq!(data.as_ptr(), lp.as_ptr(), "pass-through must not copy");
}
#[test]
fn a4_trailing_zero_fixture_matches_master_movenc_output() {
let (runtime, _, _) = AvcRuntime::from_extradata(&annexb_config(), 0).unwrap();
let au = vec![
0, 0, 0, 1, 0x06, 0x05, 0xFF, 0, 0, 0, 0, 1, 0x65, 0x88, 0x84, 0, ];
let mut scratch = Vec::new();
let (is_key, data) = runtime.normalize_au(&au, &mut scratch, 0).unwrap();
assert!(is_key);
assert_eq!(
data,
&[0, 0, 0, 3, 0x06, 0x05, 0xFF, 0, 0, 0, 3, 0x65, 0x88, 0x84]
);
assert_eq!(data.len(), 14);
assert!(scratch.capacity() >= 14);
}
#[test]
fn annexb_config_split_reuses_the_walker() {
let config = annexb_config();
let nals = collect_annexb(&config).unwrap();
assert_eq!(nals, vec![SPS, PPS]);
}
fn raw_avcc(profile: u8, compat: u8, level: u8, sps: &[u8], pps: &[u8]) -> Vec<u8> {
let mut v = vec![1, profile, compat, level, 0xFF, 0xE1];
v.extend_from_slice(&(sps.len() as u16).to_be_bytes());
v.extend_from_slice(sps);
v.push(1);
v.extend_from_slice(&(pps.len() as u16).to_be_bytes());
v.extend_from_slice(pps);
v
}
#[test]
fn rejects_avcc_with_swapped_sps_pps_nal_types() {
let swapped = raw_avcc(66, 0xC0, 0x1E, PPS, SPS);
let err = parse_avcc_parameter_sets(&swapped).unwrap_err();
assert!(err.contains("NAL type"), "unexpected error: {err}");
assert!(matches!(
AvcRuntime::from_extradata(&swapped, 3),
Err(PacketSinkError::InvalidExtradata { stream_index: 3, .. })
));
let bad_pps = raw_avcc(66, 0xC0, 0x1E, SPS, SPS);
let err = parse_avcc_parameter_sets(&bad_pps).unwrap_err();
assert!(err.contains("NAL type"), "unexpected error: {err}");
}
#[test]
fn rejects_sps_bit_depth_ue_overflow() {
let sps = [
0x67, 100, 0x00, 30, 0xA0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0,
];
let sets = ParameterSets {
sps: vec![sps.to_vec()],
pps: vec![PPS.to_vec()],
};
let err = build_avcc(&sets).unwrap_err();
assert!(err.contains("bit depth"), "unexpected error: {err}");
}
#[test]
fn rejects_chroma_format_idc_above_three() {
for (payload, idc) in [(0x88u8, 7u32), (0x94, 4)] {
let sps = [0x67, 100, 0x00, 30, payload];
let sets = ParameterSets {
sps: vec![sps.to_vec()],
pps: vec![PPS.to_vec()],
};
let err = build_avcc(&sets).unwrap_err();
assert!(
err.contains(&format!("chroma_format_idc {idc}")),
"unexpected error: {err}"
);
}
}
#[test]
fn rejects_truncated_configuration_data() {
let avcc = build_avcc(&parse_parameter_sets(&annexb_config()).unwrap().sets).unwrap();
for cut in 0..avcc.len() {
assert!(
parse_avcc_parameter_sets(&avcc[..cut]).is_err(),
"a {cut}-byte prefix must be rejected"
);
}
let sets = ParameterSets {
sps: vec![vec![0x67, 100, 0x00, 30]],
pps: vec![PPS.to_vec()],
};
let err = build_avcc(&sets).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
}
#[test]
fn rejects_avcc_header_disagreeing_with_first_sps() {
let good = build_avcc(&parse_parameter_sets(&annexb_config()).unwrap().sets).unwrap();
assert!(AvcRuntime::from_extradata(&good, 0).is_ok());
for byte in 1..4 {
let mut bad = good.clone();
bad[byte] ^= 0x01;
assert!(
matches!(
AvcRuntime::from_extradata(&bad, 5),
Err(PacketSinkError::InvalidExtradata { stream_index: 5, .. })
),
"tampered header byte {byte} must be rejected"
);
}
}
fn high_sets() -> ParameterSets {
ParameterSets {
sps: vec![HIGH_SPS.to_vec()],
pps: vec![HIGH_PPS.to_vec()],
}
}
#[test]
fn rejects_avcc_reserved_bits_cleared() {
let good = build_avcc(&parse_parameter_sets(&annexb_config()).unwrap().sets).unwrap();
let mut bad = good.clone();
bad[4] = 0x03;
let err = parse_avcc_parameter_sets(&bad).unwrap_err();
assert!(err.contains("byte 4 reserved"), "unexpected error: {err}");
let mut bad = good.clone();
bad[5] = 0x01;
let err = parse_avcc_parameter_sets(&bad).unwrap_err();
assert!(err.contains("byte 5 reserved"), "unexpected error: {err}");
assert!(matches!(
AvcRuntime::from_extradata(&bad, 2),
Err(PacketSinkError::InvalidExtradata { stream_index: 2, .. })
));
}
#[test]
fn rejects_parameter_set_with_forbidden_zero_bit() {
let mut sps = SPS.to_vec();
sps[0] = 0xE7;
let err = parse_avcc_parameter_sets(&raw_avcc(66, 0xC0, 0x1E, &sps, PPS)).unwrap_err();
assert!(err.contains("forbidden_zero_bit"), "unexpected error: {err}");
let mut config = vec![0, 0, 0, 1];
config.extend_from_slice(&sps);
config.extend_from_slice(&[0, 0, 1]);
config.extend_from_slice(PPS);
let err = parse_parameter_sets(&config).unwrap_err();
assert!(err.contains("forbidden_zero_bit"), "unexpected error: {err}");
let mut pps = PPS.to_vec();
pps[0] = 0xE8;
let err = parse_avcc_parameter_sets(&raw_avcc(66, 0xC0, 0x1E, SPS, &pps)).unwrap_err();
assert!(err.contains("forbidden_zero_bit"), "unexpected error: {err}");
}
#[test]
fn accepts_high_profile_avcc_without_the_extension() {
let bare = raw_avcc(0x64, 0x00, 0x1E, HIGH_SPS, HIGH_PPS);
let record = parse_avcc_record(&bare).unwrap();
assert_eq!(record.extension, None);
assert_eq!(record.sets, high_sets());
assert!(AvcRuntime::from_extradata(&bare, 0).is_ok());
}
#[test]
fn round_trips_the_high_profile_extension() {
let avcc = build_avcc(&high_sets()).unwrap();
let record = parse_avcc_record(&avcc).unwrap();
assert_eq!(record.extension, Some((1, 8, 8)));
assert_eq!(record.sets, high_sets());
assert!(AvcRuntime::from_extradata(&avcc, 0).is_ok());
}
#[test]
fn rejects_extension_reserved_bits_cleared() {
let good = build_avcc(&high_sets()).unwrap();
let ext = good.len() - 4;
for (offset, cleared) in [(0usize, 0x01u8), (1, 0x00), (2, 0x00)] {
let mut bad = good.clone();
bad[ext + offset] = cleared;
let err = parse_avcc_parameter_sets(&bad).unwrap_err();
assert!(err.contains("reserved"), "unexpected error: {err}");
}
}
#[test]
fn rejects_partial_or_padded_extension() {
let good = build_avcc(&high_sets()).unwrap();
let err = parse_avcc_parameter_sets(&good[..good.len() - 2]).unwrap_err();
assert!(err.contains("extension truncated"), "unexpected error: {err}");
let mut padded = good.clone();
padded.push(0);
let err = parse_avcc_parameter_sets(&padded).unwrap_err();
assert!(
err.contains("after the profile extension"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_trailing_bytes_on_baseline_profile() {
let mut avcc = build_avcc(&parse_parameter_sets(&annexb_config()).unwrap().sets).unwrap();
avcc.push(0);
let err = parse_avcc_parameter_sets(&avcc).unwrap_err();
assert!(err.contains("no extension is defined"), "unexpected error: {err}");
}
#[test]
fn validates_sequence_parameter_set_ext_entries() {
let mut avcc = build_avcc(&high_sets()).unwrap();
let count = avcc.len() - 1;
avcc[count] = 1;
avcc.extend_from_slice(&[0, 2, 0x6D, 0x40]);
let record = parse_avcc_record(&avcc).unwrap();
assert_eq!(record.extension, Some((1, 8, 8)));
let header = avcc.len() - 2;
avcc[header] = 0x67;
let err = parse_avcc_record(&avcc).unwrap_err();
assert!(err.contains("SPS-EXT"), "unexpected error: {err}");
avcc[header] = 0xED;
let err = parse_avcc_record(&avcc).unwrap_err();
assert!(err.contains("forbidden_zero_bit"), "unexpected error: {err}");
let mut short = build_avcc(&high_sets()).unwrap();
let count = short.len() - 1;
short[count] = 1;
let err = parse_avcc_parameter_sets(&short).unwrap_err();
assert!(err.contains("SPS-EXT"), "unexpected error: {err}");
}
#[test]
fn rejects_extension_disagreeing_with_the_sps() {
let mut avcc = build_avcc(&high_sets()).unwrap();
let ext = avcc.len() - 4;
avcc[ext + 1] = 0xF8 | 2;
let err = parse_avcc_parameter_sets(&avcc).unwrap_err();
assert!(err.contains("bit depths"), "unexpected error: {err}");
assert!(matches!(
AvcRuntime::from_extradata(&avcc, 6),
Err(PacketSinkError::InvalidExtradata { stream_index: 6, .. })
));
}
#[test]
fn s8_rejects_tampered_avcc_header_with_unchanged_sets() {
let (runtime, delivered, _) = AvcRuntime::from_extradata(&annexb_config(), 0).unwrap();
runtime.check_new_extradata(&delivered, 0).unwrap();
for byte in 1..4 {
let mut tampered = delivered.clone();
tampered[byte] ^= 0x01;
assert!(
matches!(
runtime.check_new_extradata(&tampered, 7),
Err(PacketSinkError::ConfigChange { stream_index: 7, .. })
),
"tampered avcC header byte {byte} must be rejected"
);
}
}
#[test]
fn s8_rejects_tampered_extension_fields_with_unchanged_sets() {
let config = build_avcc(&high_sets()).unwrap();
let (runtime, delivered, _) = AvcRuntime::from_extradata(&config, 0).unwrap();
runtime.check_new_extradata(&delivered, 0).unwrap();
let mut tampered = delivered;
let ext = tampered.len() - 4;
tampered[ext] = 0xFC | 2;
assert!(matches!(
runtime.check_new_extradata(&tampered, 4),
Err(PacketSinkError::ConfigChange { stream_index: 4, .. })
));
}
#[test]
fn s8_sees_the_derived_extension_triple() {
assert_eq!(parse_sps(TEN_BIT_SPS).unwrap().sps_id, 0);
assert_eq!(parse_sps(CHROMA3_SCALING_SPS).unwrap().sps_id, 0);
assert_eq!(TEN_BIT_SPS[1..4], CHROMA3_SCALING_SPS[1..4]);
assert_eq!(parse_sps(TEN_BIT_SPS).unwrap().chroma_info(), (1, 10, 10));
assert_eq!(parse_sps(CHROMA3_SCALING_SPS).unwrap().chroma_info(), (3, 8, 8));
let ten_first = annexb_concat(&[
TEN_BIT_SPS,
CHROMA3_SCALING_SPS,
CHROMA3_SCALING_SPS,
MINIMAL_PPS,
]);
let chroma3_first = annexb_concat(&[
CHROMA3_SCALING_SPS,
TEN_BIT_SPS,
CHROMA3_SCALING_SPS,
MINIMAL_PPS,
]);
let a = parse_parameter_sets(&ten_first).unwrap().fingerprint;
let b = parse_parameter_sets(&chroma3_first).unwrap().fingerprint;
assert_eq!(a.sps, b.sps);
assert_eq!(a.pps, b.pps);
assert_eq!(a.sps_ext, b.sps_ext);
assert_ne!(a.extension, b.extension, "the first SPS decides the triple");
let (runtime, delivered, _) = AvcRuntime::from_extradata(&ten_first, 0).unwrap();
assert_eq!(&delivered[delivered.len() - 4..], &[0xFD, 0xFA, 0xFA, 0x00]);
let (_, delivered, _) = AvcRuntime::from_extradata(&chroma3_first, 0).unwrap();
assert_eq!(&delivered[delivered.len() - 4..], &[0xFF, 0xF8, 0xF8, 0x00]);
runtime.check_new_extradata(&ten_first, 0).unwrap();
assert!(matches!(
runtime.check_new_extradata(&chroma3_first, 5),
Err(PacketSinkError::ConfigChange { stream_index: 5, .. })
));
}
#[test]
fn s8_distinguishes_same_id_reorder_from_distinct_id_reorder() {
let config = annexb_concat(&[SPS, SAME_PROJ_SPS, PPS]);
let (runtime, _, _) = AvcRuntime::from_extradata(&config, 0).unwrap();
runtime.check_new_extradata(&config, 0).unwrap();
let swapped = annexb_concat(&[SAME_PROJ_SPS, SPS, PPS]);
assert!(matches!(
runtime.check_new_extradata(&swapped, 5),
Err(PacketSinkError::ConfigChange { stream_index: 5, .. })
));
let config = annexb_concat(&[SPS, SPS_ID_31, PPS]);
let (runtime, _, _) = AvcRuntime::from_extradata(&config, 0).unwrap();
let swapped = annexb_concat(&[SPS_ID_31, SPS, PPS]);
runtime.check_new_extradata(&swapped, 0).unwrap();
}
#[test]
fn s8_sees_the_sps_generation_each_pps_bound() {
assert_eq!(parse_sps(SPS).unwrap().sps_id, 0);
assert_eq!(parse_sps(SAME_PROJ_SPS).unwrap().sps_id, 0);
assert_eq!(SPS[1..4], SAME_PROJ_SPS[1..4]);
assert_ne!(SPS[4..], SAME_PROJ_SPS[4..]);
let baseline = annexb_with(SPS, PPS);
let announced = annexb_concat(&[SAME_PROJ_SPS, PPS, SPS]);
let fp = |config: &[u8]| parse_parameter_sets(config).unwrap().fingerprint;
assert_ne!(
fp(&baseline),
fp(&announced),
"the PPS binding must be part of the fingerprint"
);
let (runtime, _, _) = AvcRuntime::from_extradata(&baseline, 0).unwrap();
runtime.check_new_extradata(&baseline, 0).unwrap();
assert!(matches!(
runtime.check_new_extradata(&announced, 5),
Err(PacketSinkError::ConfigChange { stream_index: 5, .. })
));
let bound_b = annexb_concat(&[SPS, SAME_PROJ_SPS, PPS]);
let bound_a = annexb_concat(&[SPS, PPS, SAME_PROJ_SPS]);
assert_ne!(
fp(&bound_b),
fp(&bound_a),
"equal SPS lists with a different binding must not collapse"
);
let (runtime, _, _) = AvcRuntime::from_extradata(&bound_b, 0).unwrap();
runtime.check_new_extradata(&bound_b, 0).unwrap();
assert!(matches!(
runtime.check_new_extradata(&bound_a, 5),
Err(PacketSinkError::ConfigChange { stream_index: 5, .. })
));
for config in [
annexb_concat(&[SPS, SPS_ID_31, PPS]),
annexb_concat(&[SPS_ID_31, SPS, PPS]),
] {
let prints = fp(&config);
let (_, (_, bound)) = &prints.pps[0];
assert_eq!(
bound.as_slice(),
&SPS[1..],
"the PPS slot must record the id-0 generation it bound"
);
}
}
#[test]
fn header_only_nal_ref_idc_changes_stay_redundant() {
let mut sps_idc2 = SPS.to_vec();
sps_idc2[0] = 0x47; let mut pps_idc2 = PPS.to_vec();
pps_idc2[0] = 0x48; let (runtime, delivered, _) = AvcRuntime::from_extradata(&annexb_config(), 0).unwrap();
assert_eq!(&delivered[8..8 + SPS.len()], SPS);
runtime
.check_new_extradata(&annexb_with(&sps_idc2, PPS), 0)
.unwrap();
runtime
.check_new_extradata(&annexb_with(SPS, &pps_idc2), 0)
.unwrap();
let mut au = vec![0, 0, 0, 1];
au.extend_from_slice(&sps_idc2);
au.extend_from_slice(&[0, 0, 1, 0x65, 0x88, 0x80]);
let mut scratch = Vec::new();
assert!(matches!(
runtime.normalize_au(&au, &mut scratch, 1),
Err(PacketSinkError::InBandParameterSets { stream_index: 1 })
));
let mut au = vec![0, 0, 0, 1];
au.extend_from_slice(&pps_idc2);
au.extend_from_slice(&[0, 0, 1, 0x65, 0x88, 0x80]);
assert!(matches!(
runtime.normalize_au(&au, &mut scratch, 1),
Err(PacketSinkError::InBandParameterSets { stream_index: 1 })
));
let config = annexb_concat(&[SPS, &sps_idc2, PPS]);
let (runtime, _, _) = AvcRuntime::from_extradata(&config, 0).unwrap();
runtime
.check_new_extradata(&annexb_concat(&[&sps_idc2, SPS, PPS]), 0)
.unwrap();
}
#[test]
fn rejects_annexb_pps_ahead_of_its_sps() {
let config = annexb_concat(&[PPS, SPS]);
let err = parse_parameter_sets(&config).unwrap_err();
assert!(err.contains("no preceding SPS"), "unexpected error: {err}");
assert!(matches!(
AvcRuntime::from_extradata(&config, 2),
Err(PacketSinkError::InvalidExtradata { stream_index: 2, .. })
));
}
#[test]
fn pps_binds_the_preceding_sps_not_a_later_one() {
let forward = annexb_concat(&[HIGH_SPS, SCALING_TAIL_8_PPS, CHROMA3_SCALING_SPS]);
parse_parameter_sets(&forward).unwrap();
let flipped = annexb_concat(&[CHROMA3_SCALING_SPS, SCALING_TAIL_8_PPS, HIGH_SPS]);
let err = parse_parameter_sets(&flipped).unwrap_err();
assert!(err.starts_with("PPS:"), "unexpected error: {err}");
}
#[test]
fn from_extradata_rejects_interleavings_with_no_faithful_avcc() {
let reason = |config: &[u8]| match AvcRuntime::from_extradata(config, 4) {
Ok(_) => panic!("an unrepresentable interleaving must be rejected"),
Err(PacketSinkError::InvalidExtradata {
stream_index: 4,
reason,
}) => reason,
Err(other) => panic!("unexpected error: {other}"),
};
let rebind_breaks = annexb_concat(&[HIGH_SPS, SCALING_TAIL_8_PPS, CHROMA3_SCALING_SPS]);
parse_parameter_sets(&rebind_breaks).unwrap(); let r = reason(&rebind_breaks);
assert!(
r.contains("no faithful avcC representation"),
"unexpected reason: {r}"
);
let rebind_shifts = annexb_concat(&[SPS, PPS, SAME_PROJ_SPS]);
parse_parameter_sets(&rebind_shifts).unwrap(); let r = reason(&rebind_shifts);
assert!(
r.contains("no faithful avcC representation"),
"unexpected reason: {r}"
);
assert!(AvcRuntime::from_extradata(&annexb_config(), 0).is_ok());
assert!(AvcRuntime::from_extradata(&annexb_with(HIGH_SPS, HIGH_PPS), 0).is_ok());
}
#[test]
fn s8_sees_sps_ext_entries() {
let mut with_ext = build_avcc(&high_sets()).unwrap();
let count = with_ext.len() - 1;
with_ext[count] = 1;
with_ext.extend_from_slice(&[0, 2, 0x6D, 0x40]);
let (runtime, delivered, _) = AvcRuntime::from_extradata(&with_ext, 0).unwrap();
runtime.check_new_extradata(&delivered, 0).unwrap();
let mut mutated = with_ext.clone();
let last = mutated.len() - 1;
mutated[last] = 0x41;
assert!(matches!(
runtime.check_new_extradata(&mutated, 3),
Err(PacketSinkError::ConfigChange { stream_index: 3, .. })
));
let without = build_avcc(&high_sets()).unwrap();
assert!(matches!(
runtime.check_new_extradata(&without, 3),
Err(PacketSinkError::ConfigChange { stream_index: 3, .. })
));
let (runtime, _, _) = AvcRuntime::from_extradata(&without, 0).unwrap();
assert!(matches!(
runtime.check_new_extradata(&with_ext, 3),
Err(PacketSinkError::ConfigChange { stream_index: 3, .. })
));
}
#[test]
fn s8_keys_sps_ext_by_post_header_payload() {
let with_ext = |header: u8, body: u8| {
let mut avcc = build_avcc(&high_sets()).unwrap();
let count = avcc.len() - 1;
avcc[count] = 1;
avcc.extend_from_slice(&[0, 2, header, body]);
avcc
};
let (runtime, delivered, _) =
AvcRuntime::from_extradata(&with_ext(0x6D, 0x40), 0).unwrap();
runtime.check_new_extradata(&delivered, 0).unwrap();
runtime.check_new_extradata(&with_ext(0x4D, 0x40), 0).unwrap();
assert!(matches!(
runtime.check_new_extradata(&with_ext(0x6D, 0x41), 3),
Err(PacketSinkError::ConfigChange { stream_index: 3, .. })
));
}
#[test]
fn inband_replaced_predecessor_is_a_config_change() {
let config = annexb_concat(&[SPS, SAME_PROJ_SPS, PPS]);
let (runtime, _, _) = AvcRuntime::from_extradata(&config, 0).unwrap();
let mut scratch = Vec::new();
let mut au = vec![0, 0, 0, 1];
au.extend_from_slice(SPS); au.extend_from_slice(&[0, 0, 1, 0x65, 0x88, 0x80]);
assert!(matches!(
runtime.normalize_au(&au, &mut scratch, 1),
Err(PacketSinkError::ConfigChange { stream_index: 1, .. })
));
let mut au = vec![0, 0, 0, 1];
au.extend_from_slice(SAME_PROJ_SPS); au.extend_from_slice(&[0, 0, 1, 0x65, 0x88, 0x80]);
assert!(matches!(
runtime.normalize_au(&au, &mut scratch, 1),
Err(PacketSinkError::InBandParameterSets { stream_index: 1 })
));
}
#[test]
fn rejects_nonzero_reserved_zero_2bits() {
let mut sps = SPS.to_vec();
sps[2] |= 0x01;
let err = parse_sps(&sps).unwrap_err();
assert!(err.contains("reserved_zero_2bits"), "unexpected error: {err}");
let config = annexb_with(&sps, PPS);
assert!(matches!(
AvcRuntime::from_extradata(&config, 6),
Err(PacketSinkError::InvalidExtradata { stream_index: 6, .. })
));
}
#[test]
fn rejects_sps_truncated_after_the_chroma_block_on_both_paths() {
let cut = &HIGH_SPS[..5]; let mut config = vec![0, 0, 0, 1];
config.extend_from_slice(cut);
config.extend_from_slice(&[0, 0, 1]);
config.extend_from_slice(HIGH_PPS);
let err = parse_parameter_sets(&config).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
let avcc = raw_avcc(0x64, 0x00, 0x1E, cut, HIGH_PPS);
let err = parse_avcc_parameter_sets(&avcc).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
}
#[test]
fn rejects_sps_truncated_mid_dimensions() {
let cut = &SPS[..6];
let err = parse_sps(cut).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
let mut config = vec![0, 0, 0, 1];
config.extend_from_slice(cut);
config.extend_from_slice(&[0, 0, 1]);
config.extend_from_slice(PPS);
let err = parse_parameter_sets(&config).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
}
#[test]
fn rejects_seq_parameter_set_id_above_31() {
assert_eq!(parse_sps(SPS_ID_31).unwrap().chroma_info(), (1, 8, 8));
assert_eq!(parse_sps(SPS_ID_31).unwrap().sps_id, 31);
let err = parse_sps(SPS_ID_32).unwrap_err();
assert!(err.contains("seq_parameter_set_id 32"), "unexpected error: {err}");
}
#[test]
fn rejects_out_of_range_frame_num_and_poc_fields() {
assert_eq!(parse_sps(LOG2_FRAME_NUM_12).unwrap().chroma_info(), (1, 8, 8));
let err = parse_sps(LOG2_FRAME_NUM_13).unwrap_err();
assert!(
err.contains("log2_max_frame_num_minus4 13"),
"unexpected error: {err}"
);
let err = parse_sps(POC_TYPE_3).unwrap_err();
assert!(err.contains("pic_order_cnt_type 3"), "unexpected error: {err}");
assert_eq!(parse_sps(POC_CYCLE_255).unwrap().chroma_info(), (1, 8, 8));
let err = parse_sps(POC_CYCLE_256).unwrap_err();
assert!(
err.contains("num_ref_frames_in_pic_order_cnt_cycle 256"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_declared_but_truncated_tail_structures() {
let err = parse_sps(MISSING_CROP_SPS).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
let err = parse_sps(VUI_TRUNCATED_MIN_SPS).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
let err = parse_sps(&HIGH_SPS[..12]).unwrap_err();
assert!(err.contains("truncated"), "unexpected error: {err}");
}
#[test]
fn rejects_corrupt_rbsp_trailing_bits() {
let err = parse_sps(BAD_STOP_BIT_SPS).unwrap_err();
assert!(err.contains("stop bit"), "unexpected error: {err}");
let mut config = vec![0, 0, 0, 1];
config.extend_from_slice(BAD_STOP_BIT_SPS);
config.extend_from_slice(&[0, 0, 1]);
config.extend_from_slice(HIGH_PPS);
assert!(matches!(
AvcRuntime::from_extradata(&config, 9),
Err(PacketSinkError::InvalidExtradata { stream_index: 9, .. })
));
}
#[test]
fn accepts_scaling_matrix_sps_variants() {
assert_eq!(parse_sps(HIGH_SPS_SCALING).unwrap().chroma_info(), (1, 8, 8));
assert_eq!(parse_sps(SCALING_LIST_SPS).unwrap().chroma_info(), (1, 8, 8));
assert_eq!(parse_sps(CHROMA3_SCALING_SPS).unwrap().chroma_info(), (3, 8, 8));
let sets = ParameterSets {
sps: vec![HIGH_SPS_SCALING.to_vec()],
pps: vec![HIGH_PPS.to_vec()],
};
let avcc = build_avcc(&sets).unwrap();
assert!(AvcRuntime::from_extradata(&avcc, 0).is_ok());
let tail = &avcc[avcc.len() - 4..];
assert_eq!(tail[..3], [0xFC | 1, 0xF8, 0xF8]);
}
#[test]
fn accepts_profile_144_with_the_chroma_block() {
assert_eq!(parse_sps(PROFILE_144_SPS).unwrap().chroma_info(), (1, 8, 8));
let sets = ParameterSets {
sps: vec![PROFILE_144_SPS.to_vec()],
pps: vec![HIGH_PPS.to_vec()],
};
let avcc = build_avcc(&sets).unwrap();
assert!(AvcRuntime::from_extradata(&avcc, 0).is_ok());
}
#[test]
fn synthesizes_the_writer_default_triple_outside_the_writer_dispatch() {
assert_eq!(
parse_sps(PROFILE_144_CHROMA3_SPS).unwrap().chroma_info(),
(3, 8, 8)
);
assert_eq!(
parse_sps(PROFILE_135_TEN_BIT_SPS).unwrap().chroma_info(),
(1, 10, 10)
);
for sps in [PROFILE_144_CHROMA3_SPS, PROFILE_135_TEN_BIT_SPS] {
assert_eq!(
derived_extension(sps, &parse_sps(sps).unwrap()),
Some((1, 8, 8))
);
let sets = ParameterSets {
sps: vec![sps.to_vec()],
pps: vec![MINIMAL_PPS.to_vec()],
};
let avcc = build_avcc(&sets).unwrap();
assert_eq!(avcc[avcc.len() - 4..], [0xFD, 0xF8, 0xF8, 0x00]);
let (_, delivered, _) =
AvcRuntime::from_extradata(&annexb_with(sps, MINIMAL_PPS), 0).unwrap();
assert_eq!(delivered, avcc);
}
}
#[test]
fn accepts_ffmpeg_shaped_records_outside_the_writer_dispatch() {
let mut avcc = raw_avcc(144, 0x00, 0x1E, PROFILE_144_CHROMA3_SPS, MINIMAL_PPS);
avcc.extend_from_slice(&[0xFD, 0xF8, 0xF8, 0x00]);
let record = parse_avcc_record(&avcc).unwrap();
assert_eq!(record.extension, Some((1, 8, 8)));
assert!(AvcRuntime::from_extradata(&avcc, 0).is_ok());
let annexb = annexb_with(PROFILE_144_CHROMA3_SPS, MINIMAL_PPS);
let (runtime, _, _) = AvcRuntime::from_extradata(&annexb, 0).unwrap();
runtime.check_new_extradata(&avcc, 0).unwrap();
let ext = avcc.len() - 4;
let mut passthrough = avcc.clone();
passthrough[ext] = 0xFC | 3;
let passthrough_record = parse_avcc_record(&passthrough).unwrap();
assert_eq!(passthrough_record.extension, Some((3, 8, 8)));
assert_eq!(passthrough_record.fingerprint, record.fingerprint);
assert!(AvcRuntime::from_extradata(&passthrough, 0).is_ok());
runtime.check_new_extradata(&passthrough, 0).unwrap();
let (from_synthesis, _, _) = AvcRuntime::from_extradata(&avcc, 0).unwrap();
from_synthesis.check_new_extradata(&passthrough, 0).unwrap();
let mut avcc135 = raw_avcc(135, 0x00, 0x1E, PROFILE_135_TEN_BIT_SPS, MINIMAL_PPS);
avcc135.extend_from_slice(&[0xFD, 0xF8, 0xF8, 0x00]);
let writer_shaped = parse_avcc_record(&avcc135).unwrap();
let ext135 = avcc135.len() - 4;
let mut passthrough135 = avcc135.clone();
passthrough135[ext135 + 1] = 0xF8 | 2;
passthrough135[ext135 + 2] = 0xF8 | 2;
let syntax_shaped = parse_avcc_record(&passthrough135).unwrap();
assert_eq!(syntax_shaped.extension, Some((1, 10, 10)));
assert_eq!(syntax_shaped.fingerprint, writer_shaped.fingerprint);
let mut neither = avcc.clone();
neither[ext] = 0xFC | 2;
let err = parse_avcc_parameter_sets(&neither).unwrap_err();
assert!(
err.contains("chroma_format_idc 2")
&& err.contains("(writer default)")
&& err.contains("(SPS syntax)"),
"unexpected error: {err}"
);
}
#[test]
fn parses_real_and_minimal_pps_bodies() {
parse_pps(PPS, &sps_ctx(&[SPS])).unwrap();
parse_pps(HIGH_PPS, &sps_ctx(&[HIGH_SPS])).unwrap();
parse_pps(MINIMAL_PPS, &sps_ctx(&[SPS])).unwrap();
}
#[test]
fn rejects_truncated_pps_on_both_paths() {
let cut = &PPS[..3];
let err = parse_parameter_sets(&annexb_with(SPS, cut)).unwrap_err();
assert!(
err.contains("PPS") && err.contains("truncated"),
"unexpected error: {err}"
);
let err = parse_avcc_parameter_sets(&raw_avcc(66, 0xC0, 0x1E, SPS, cut)).unwrap_err();
assert!(
err.contains("PPS") && err.contains("truncated"),
"unexpected error: {err}"
);
assert!(matches!(
AvcRuntime::from_extradata(&annexb_with(SPS, cut), 4),
Err(PacketSinkError::InvalidExtradata { stream_index: 4, .. })
));
}
#[test]
fn rejects_pps_with_corrupt_rbsp_trailing_bits() {
let err = parse_pps(BAD_STOP_BIT_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(err.contains("stop bit"), "unexpected error: {err}");
let avcc = raw_avcc(66, 0xC0, 0x1E, SPS, BAD_STOP_BIT_PPS);
let err = parse_avcc_parameter_sets(&avcc).unwrap_err();
assert!(err.contains("stop bit"), "unexpected error: {err}");
assert!(matches!(
AvcRuntime::from_extradata(&avcc, 8),
Err(PacketSinkError::InvalidExtradata { stream_index: 8, .. })
));
}
#[test]
fn rejects_pic_parameter_set_id_above_255() {
parse_pps(PPS_ID_255_PPS, &sps_ctx(&[SPS])).unwrap();
let err = parse_pps(PPS_ID_256_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(
err.contains("pic_parameter_set_id 256"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_pps_sps_reference_out_of_range_or_dangling() {
let err = parse_pps(SPS_ID_32_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(
err.contains("seq_parameter_set_id 32"),
"unexpected error: {err}"
);
parse_pps(SPS_ID_31_PPS, &sps_ctx(&[SPS_ID_31])).unwrap();
let err = parse_pps(SPS_ID_31_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(err.contains("no preceding SPS"), "unexpected error: {err}");
}
#[test]
fn rejects_weighted_bipred_idc_three() {
parse_pps(WEIGHTED_BIPRED_2_PPS, &sps_ctx(&[SPS])).unwrap();
let err = parse_pps(WEIGHTED_BIPRED_3_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(
err.contains("weighted_bipred_idc 3"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_out_of_range_pps_qp_fields() {
let ctx = sps_ctx(&[SPS]);
parse_pps(CHROMA_QP_12_PPS, &ctx).unwrap();
let err = parse_pps(CHROMA_QP_13_PPS, &ctx).unwrap_err();
assert!(
err.contains("chroma_qp_index_offset 13"),
"unexpected error: {err}"
);
parse_pps(INIT_QP_M26_PPS, &ctx).unwrap();
for (fixture, qp) in [
(INIT_QP_M27_PPS, -27),
(INIT_QP_M32_PPS, -32),
(INIT_QP_M88_PPS, -88),
(INIT_QP_M89_PPS, -89),
] {
let err = parse_pps(fixture, &ctx).unwrap_err();
assert!(
err.contains(&format!("pic_init_qp_minus26 {qp}")),
"unexpected error: {err}"
);
}
parse_pps(INIT_QP_M32_PPS, &sps_ctx(&[TEN_BIT_SPS])).unwrap();
let err = parse_pps(INIT_QS_M27_PPS, &ctx).unwrap_err();
assert!(
err.contains("pic_init_qs_minus26 -27"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_pps_reference_count_overflow() {
let err = parse_pps(REF_IDX_L0_32_PPS, &sps_ctx(&[SPS])).unwrap_err();
assert!(err.contains("num_ref_idx"), "unexpected error: {err}");
}
#[test]
fn pps_scaling_block_count_follows_the_referenced_sps() {
let chroma1 = sps_ctx(&[HIGH_SPS]);
let chroma3 = sps_ctx(&[CHROMA3_SCALING_SPS]);
parse_pps(SCALING_TAIL_8_PPS, &chroma1).unwrap();
assert!(parse_pps(SCALING_TAIL_8_PPS, &chroma3).is_err());
parse_pps(SCALING_TAIL_12_PPS, &chroma3).unwrap();
assert!(parse_pps(SCALING_TAIL_12_PPS, &chroma1).is_err());
}
#[test]
fn parses_the_slice_group_map_types() {
let ctx = sps_ctx(&[SPS]);
for (i, fixture) in [FMO_TYPE0_PPS, FMO_TYPE2_PPS, FMO_TYPE3_PPS]
.iter()
.enumerate()
{
parse_pps(fixture, &ctx).unwrap_or_else(|e| panic!("FMO fixture {i}: {e}"));
}
parse_pps(FMO_TYPE6_PPS, &sps_ctx(&[MAP_UNITS_4_SPS]))
.unwrap_or_else(|e| panic!("FMO type-6 fixture: {e}"));
let err = parse_pps(FMO_GROUPS_8_PPS, &ctx).unwrap_err();
assert!(
err.contains("num_slice_groups_minus1 8"),
"unexpected error: {err}"
);
let err = parse_pps(FMO_TYPE7_PPS, &ctx).unwrap_err();
assert!(
err.contains("slice_group_map_type 7"),
"unexpected error: {err}"
);
}
#[test]
fn bounds_the_slice_group_map_against_the_referenced_sps() {
let ctx = sps_ctx(&[SPS]);
for (fixture, needle) in [
(FMO_TYPE0_RUN_300_PPS, "run_length_minus1 300"),
(FMO_TYPE2_BR_300_PPS, "bottom_right 300"),
(FMO_RATE_300_PPS, "slice_group_change_rate_minus1 300"),
(FMO_TYPE6_SIZE_300_PPS, "declares 301 map units"),
(FMO_TYPE6_PPS, "declares 4 map units"),
(FMO_TYPE2_INVERTED_PPS, "rectangle 40..20 is inverted"),
(FMO_TYPE2_COLUMN_PPS, "rectangle 1..20 is inverted"),
(FMO_TYPE3_GROUPS_3_PPS, "requires exactly two slice groups"),
] {
let err = parse_pps(fixture, &ctx).unwrap_err();
assert!(err.contains(needle), "expected {needle:?}, got: {err}");
}
let err = parse_pps(FMO_TYPE6_ID_3_PPS, &sps_ctx(&[MAP_UNITS_4_SPS])).unwrap_err();
assert!(err.contains("slice_group_id 3"), "unexpected error: {err}");
}
#[test]
fn rejects_max_num_ref_frames_above_16() {
assert_eq!(parse_sps(REF_FRAMES_16_SPS).unwrap().chroma_info(), (1, 8, 8));
let err = parse_sps(REF_FRAMES_17_SPS).unwrap_err();
assert!(err.contains("max_num_ref_frames 17"), "unexpected error: {err}");
}
#[test]
fn rejects_oversized_coded_dimensions() {
let summary = parse_sps(WIDTH_65520_SPS).unwrap();
assert_eq!(
(summary.pic_width_in_mbs, summary.pic_height_in_map_units),
(4095, 1)
);
let err = parse_sps(WIDTH_65536_SPS).unwrap_err();
assert!(err.contains("65536x16"), "unexpected error: {err}");
let err = parse_sps(PIXEL_PRODUCT_SPS).unwrap_err();
assert!(
err.contains("65520x65520") && err.contains("buffer"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_cropping_that_removes_the_whole_picture() {
assert_eq!(parse_sps(NEAR_FULL_CROP_SPS).unwrap().chroma_info(), (1, 8, 8));
let err = parse_sps(FULL_CROP_SPS).unwrap_err();
assert!(
err.contains("removes the whole"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_out_of_range_vui_fields() {
assert_eq!(parse_sps(REORDER_16_SPS).unwrap().chroma_info(), (1, 8, 8));
assert_eq!(parse_sps(CHROMA_LOC_5_SPS).unwrap().chroma_info(), (1, 8, 8));
assert_eq!(
parse_sps(REF_EQUALS_BUFFERING_SPS).unwrap().chroma_info(),
(1, 8, 8)
);
for (fixture, needle) in [
(REORDER_17_SPS, "max_num_reorder_frames 17"),
(REORDER_ABOVE_BUFFERING_SPS, "max_num_reorder_frames 2"),
(BUFFERING_17_SPS, "max_dec_frame_buffering 17"),
(BUFFERING_BELOW_REF_SPS, "max_dec_frame_buffering 0"),
(BYTES_DENOM_17_SPS, "max_bytes_per_pic_denom 17"),
(MB_DENOM_17_SPS, "max_bits_per_mb_denom 17"),
(MV_LEN_17_SPS, "log2_max_mv_length 17/0"),
(ZERO_TIME_SCALE_SPS, "timing_info 1/0"),
(ZERO_NUM_UNITS_SPS, "timing_info 0/25"),
(CHROMA_LOC_6_SPS, "chroma_sample_loc_type 6/0"),
] {
let err = parse_sps(fixture).unwrap_err();
assert!(err.contains(needle), "expected {needle:?}, got: {err}");
}
}
#[test]
fn rejects_delta_scale_outside_range() {
assert_eq!(
parse_sps(DELTA_SCALE_M128_SPS).unwrap().chroma_info(),
(1, 8, 8)
);
let err = parse_sps(DELTA_SCALE_128_SPS).unwrap_err();
assert!(err.contains("delta_scale 128"), "unexpected error: {err}");
}
}