use crate::bitreader::BitReader;
use crate::sequence_header::{SequenceHeader, SELECT_INTEGER_MV, SELECT_SCREEN_CONTENT_TOOLS};
use crate::tile_info::{read_tile_info, TileInfo};
use crate::uncompressed_header_tail::{
prev_gm_params_default, read_cdef_params, read_delta_lf_params, read_delta_q_params,
read_film_grain_params, read_global_motion_params, read_interpolation_filter,
read_loop_filter_params, read_lr_params, read_quantization_params, read_segmentation_params,
read_tx_mode, CdefParams, DeltaLfParams, DeltaQParams, FilmGrainContext, FilmGrainParams,
GlobalMotionParams, InterpolationFilter, LoopFilterParams, LrParams, QuantizationParams,
SegmentationParams, TxMode, ALTREF_FRAME, LAST_FRAME, MAX_SEGMENTS, REFS_PER_FRAME,
SEG_LVL_ALT_Q,
};
use crate::Error;
pub const NUM_REF_FRAMES: u8 = 8;
pub const PRIMARY_REF_NONE: u8 = 7;
const ALL_FRAMES: u8 = 0xff;
pub const ALL_FRAMES_PUB: u8 = ALL_FRAMES;
pub const SUPERRES_NUM: u32 = 8;
pub const SUPERRES_DENOM_MIN: u32 = 9;
pub const SUPERRES_DENOM_BITS: u32 = 3;
const LAST2_FRAME: usize = 2;
const LAST3_FRAME: usize = 3;
const GOLDEN_FRAME: usize = 4;
const BWDREF_FRAME: usize = 5;
const ALTREF2_FRAME: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameType {
Key,
Inter,
IntraOnly,
Switch,
}
impl FrameType {
pub fn from_raw(raw: u8) -> Self {
match raw & 0x3 {
0 => Self::Key,
1 => Self::Inter,
2 => Self::IntraOnly,
_ => Self::Switch,
}
}
pub fn as_raw(&self) -> u8 {
match self {
Self::Key => 0,
Self::Inter => 1,
Self::IntraOnly => 2,
Self::Switch => 3,
}
}
pub fn is_intra(&self) -> bool {
matches!(self, Self::Key | Self::IntraOnly)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameSize {
pub frame_width: u32,
pub frame_height: u32,
pub render_width: u32,
pub render_height: u32,
pub superres_denom: u32,
pub upscaled_width: u32,
pub mi_cols: u32,
pub mi_rows: u32,
pub use_superres: bool,
pub coded_denom: u8,
pub render_and_frame_size_different: bool,
}
impl FrameSize {
pub fn is_super_resolved(&self) -> bool {
self.use_superres && self.frame_width != self.upscaled_width
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefInfo {
pub valid: [bool; NUM_REF_FRAMES as usize],
pub order_hint: [u32; NUM_REF_FRAMES as usize],
pub frame_id: [u32; NUM_REF_FRAMES as usize],
pub upscaled_width: [u32; NUM_REF_FRAMES as usize],
pub frame_height: [u32; NUM_REF_FRAMES as usize],
pub render_width: [u32; NUM_REF_FRAMES as usize],
pub render_height: [u32; NUM_REF_FRAMES as usize],
pub frame_type_is_key: [bool; NUM_REF_FRAMES as usize],
pub saved_gm_params: [[[i32; 6]; crate::uncompressed_header_tail::TOTAL_REFS_PER_FRAME];
NUM_REF_FRAMES as usize],
pub saved_lf_ref_deltas:
[[i8; crate::uncompressed_header_tail::TOTAL_REFS_PER_FRAME]; NUM_REF_FRAMES as usize],
pub saved_lf_mode_deltas: [[i8; 2]; NUM_REF_FRAMES as usize],
pub saved_seg_feature_active: [[[bool; crate::uncompressed_header_tail::SEG_LVL_MAX];
crate::uncompressed_header_tail::MAX_SEGMENTS];
NUM_REF_FRAMES as usize],
pub saved_seg_feature_data: [[[i16; crate::uncompressed_header_tail::SEG_LVL_MAX];
crate::uncompressed_header_tail::MAX_SEGMENTS];
NUM_REF_FRAMES as usize],
}
impl Default for RefInfo {
fn default() -> Self {
Self {
valid: [false; NUM_REF_FRAMES as usize],
order_hint: [0; NUM_REF_FRAMES as usize],
frame_id: [0; NUM_REF_FRAMES as usize],
upscaled_width: [0; NUM_REF_FRAMES as usize],
frame_height: [0; NUM_REF_FRAMES as usize],
render_width: [0; NUM_REF_FRAMES as usize],
render_height: [0; NUM_REF_FRAMES as usize],
frame_type_is_key: [false; NUM_REF_FRAMES as usize],
saved_gm_params: [crate::uncompressed_header_tail::prev_gm_params_default();
NUM_REF_FRAMES as usize],
saved_lf_ref_deltas: [crate::uncompressed_header_tail::LOOP_FILTER_REF_DELTAS_DEFAULT;
NUM_REF_FRAMES as usize],
saved_lf_mode_deltas: [[0i8; 2]; NUM_REF_FRAMES as usize],
saved_seg_feature_active: [[[false; crate::uncompressed_header_tail::SEG_LVL_MAX];
crate::uncompressed_header_tail::MAX_SEGMENTS];
NUM_REF_FRAMES as usize],
saved_seg_feature_data: [[[0i16; crate::uncompressed_header_tail::SEG_LVL_MAX];
crate::uncompressed_header_tail::MAX_SEGMENTS];
NUM_REF_FRAMES as usize],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterFrameRefs {
pub frame_refs_short_signaling: bool,
pub last_frame_idx: Option<u8>,
pub gold_frame_idx: Option<u8>,
pub ref_frame_idx: [u8; REFS_PER_FRAME],
pub allow_high_precision_mv: bool,
pub interpolation_filter: InterpolationFilter,
pub is_motion_mode_switchable: bool,
pub use_ref_frame_mvs: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameHeader {
pub show_existing_frame: bool,
pub frame_to_show_map_idx: Option<u8>,
pub display_frame_id: Option<u32>,
pub frame_type: FrameType,
pub frame_is_intra: bool,
pub show_frame: bool,
pub showable_frame: bool,
pub error_resilient_mode: bool,
pub disable_cdf_update: bool,
pub allow_screen_content_tools: bool,
pub force_integer_mv: bool,
pub current_frame_id: u32,
pub frame_size_override_flag: bool,
pub order_hint: u32,
pub primary_ref_frame: u8,
pub refresh_frame_flags: u8,
pub ref_order_hints: Option<[u32; NUM_REF_FRAMES as usize]>,
pub frame_size: Option<FrameSize>,
pub allow_intrabc: bool,
pub disable_frame_end_update_cdf: bool,
pub tile_info: Option<TileInfo>,
pub quantization_params: Option<QuantizationParams>,
pub segmentation_params: Option<SegmentationParams>,
pub delta_q_params: Option<DeltaQParams>,
pub delta_lf_params: Option<DeltaLfParams>,
pub loop_filter_params: Option<LoopFilterParams>,
pub cdef_params: Option<CdefParams>,
pub lr_params: Option<LrParams>,
pub tx_mode: Option<TxMode>,
pub reference_select: Option<bool>,
pub skip_mode_present: Option<bool>,
pub skip_mode_frame: Option<[u8; 2]>,
pub allow_warped_motion: Option<bool>,
pub reduced_tx_set: Option<bool>,
pub global_motion_params: Option<GlobalMotionParams>,
pub film_grain_params: Option<FilmGrainParams>,
pub inter_refs: Option<InterFrameRefs>,
pub bits_consumed: usize,
}
pub fn parse_frame_header(payload: &[u8], seq: &SequenceHeader) -> Result<FrameHeader, Error> {
let mut br = BitReader::new(payload);
parse_with(&mut br, seq, &RefInfo::default())
}
pub fn parse_frame_header_with_refs(
payload: &[u8],
seq: &SequenceHeader,
ref_info: &RefInfo,
) -> Result<FrameHeader, Error> {
let mut br = BitReader::new(payload);
parse_with(&mut br, seq, ref_info)
}
fn parse_with(
br: &mut BitReader<'_>,
seq: &SequenceHeader,
ref_info: &RefInfo,
) -> Result<FrameHeader, Error> {
let id_len: u32 = if seq.frame_id_numbers_present_flag {
u32::from(seq.additional_frame_id_length_minus_1)
+ u32::from(seq.delta_frame_id_length_minus_2)
+ 3
} else {
0
};
if seq.frame_id_numbers_present_flag && id_len > 16 {
return Err(Error::InvalidIdLen);
}
if seq.reduced_still_picture_header {
let disable_cdf_update = br.f(1)? == 1;
let allow_screen_content_tools = read_allow_scc(br, seq)?;
consume_force_integer_mv_if_present(br, seq, allow_screen_content_tools)?;
let force_integer_mv = true;
let frame_size = parse_frame_size_block(br, seq, false)?;
let allow_intrabc = read_allow_intrabc(br, allow_screen_content_tools, &frame_size)?;
let disable_frame_end_update_cdf = true;
let tile_info = Some(read_tile_info(
br,
frame_size.mi_cols,
frame_size.mi_rows,
seq.use_128x128_superblock,
)?);
let quantization_params = read_quantization_params(
br,
seq.color_config.num_planes,
seq.color_config.separate_uv_delta_q,
)?;
let segmentation_params = read_segmentation_params(br, PRIMARY_REF_NONE, None)?;
let delta_q_params = read_delta_q_params(br, quantization_params.base_q_idx)?;
let delta_lf_params =
read_delta_lf_params(br, delta_q_params.delta_q_present, allow_intrabc)?;
let coded_lossless = compute_coded_lossless(&quantization_params, &segmentation_params);
let loop_filter_params = read_loop_filter_params(
br,
seq.color_config.num_planes,
coded_lossless,
allow_intrabc,
None,
)?;
let cdef_params = read_cdef_params(
br,
seq.color_config.num_planes,
coded_lossless,
allow_intrabc,
seq.enable_cdef,
)?;
let all_lossless = coded_lossless && frame_size.frame_width == frame_size.upscaled_width;
let lr_params = read_lr_params(
br,
seq.color_config.num_planes,
seq.color_config.subsampling_x,
seq.color_config.subsampling_y,
seq.use_128x128_superblock,
all_lossless,
allow_intrabc,
seq.enable_restoration,
)?;
let tx_mode = read_tx_mode(br, coded_lossless)?;
let (
reference_select,
skip_mode_present,
allow_warped_motion,
reduced_tx_set,
global_motion_params,
film_grain_params,
) = read_intra_uncompressed_header_tail(br, seq, true, false, FrameType::Key)?;
return Ok(FrameHeader {
show_existing_frame: false,
frame_to_show_map_idx: None,
display_frame_id: None,
frame_type: FrameType::Key,
frame_is_intra: true,
show_frame: true,
showable_frame: false,
error_resilient_mode: true, disable_cdf_update,
allow_screen_content_tools,
force_integer_mv,
current_frame_id: 0,
frame_size_override_flag: false,
order_hint: 0,
primary_ref_frame: PRIMARY_REF_NONE,
refresh_frame_flags: ALL_FRAMES,
ref_order_hints: None,
frame_size: Some(frame_size),
allow_intrabc,
disable_frame_end_update_cdf,
tile_info,
quantization_params: Some(quantization_params),
segmentation_params: Some(segmentation_params),
delta_q_params: Some(delta_q_params),
delta_lf_params: Some(delta_lf_params),
loop_filter_params: Some(loop_filter_params),
cdef_params: Some(cdef_params),
lr_params: Some(lr_params),
tx_mode: Some(tx_mode),
reference_select: Some(reference_select),
skip_mode_present: Some(skip_mode_present),
skip_mode_frame: None,
allow_warped_motion: Some(allow_warped_motion),
reduced_tx_set: Some(reduced_tx_set),
global_motion_params: Some(global_motion_params),
film_grain_params: Some(film_grain_params),
inter_refs: None,
bits_consumed: br.position(),
});
}
let show_existing_frame = br.f(1)? == 1;
if show_existing_frame {
let map_idx = br.f(3)? as u8;
if decoder_model_info_present(seq) && !equal_picture_interval(seq) {
return Err(Error::TemporalPointInfoUnsupported);
}
let display_id = if seq.frame_id_numbers_present_flag {
Some(br.f(id_len)? as u32)
} else {
None
};
let shown_is_key = ref_info.frame_type_is_key[map_idx as usize];
return Ok(FrameHeader {
show_existing_frame: true,
frame_to_show_map_idx: Some(map_idx),
display_frame_id: display_id,
frame_type: if shown_is_key {
FrameType::Key
} else {
FrameType::Inter
},
frame_is_intra: shown_is_key,
show_frame: true,
showable_frame: false,
error_resilient_mode: false,
disable_cdf_update: false,
allow_screen_content_tools: false,
force_integer_mv: false,
current_frame_id: 0,
frame_size_override_flag: false,
order_hint: 0,
primary_ref_frame: PRIMARY_REF_NONE,
refresh_frame_flags: if shown_is_key { ALL_FRAMES } else { 0 },
ref_order_hints: None,
frame_size: None,
allow_intrabc: false,
disable_frame_end_update_cdf: false,
tile_info: None,
quantization_params: None,
segmentation_params: None,
delta_q_params: None,
delta_lf_params: None,
loop_filter_params: None,
cdef_params: None,
lr_params: None,
tx_mode: None,
reference_select: None,
skip_mode_present: None,
skip_mode_frame: None,
allow_warped_motion: None,
reduced_tx_set: None,
global_motion_params: None,
film_grain_params: None,
inter_refs: None,
bits_consumed: br.position(),
});
}
let frame_type = FrameType::from_raw(br.f(2)? as u8);
let frame_is_intra = frame_type.is_intra();
let show_frame = br.f(1)? == 1;
if show_frame && decoder_model_info_present(seq) && !equal_picture_interval(seq) {
return Err(Error::TemporalPointInfoUnsupported);
}
let showable_frame = if show_frame {
!matches!(frame_type, FrameType::Key)
} else {
br.f(1)? == 1
};
let error_resilient_mode = if matches!(frame_type, FrameType::Switch)
|| (matches!(frame_type, FrameType::Key) && show_frame)
{
true
} else {
br.f(1)? == 1
};
let disable_cdf_update = br.f(1)? == 1;
let allow_screen_content_tools = read_allow_scc(br, seq)?;
let raw_force_integer_mv =
consume_force_integer_mv_if_present(br, seq, allow_screen_content_tools)?;
let force_integer_mv = if frame_is_intra {
true
} else {
raw_force_integer_mv
};
let current_frame_id = if seq.frame_id_numbers_present_flag {
br.f(id_len)? as u32
} else {
0
};
let frame_size_override_flag = if matches!(frame_type, FrameType::Switch) {
true
} else {
br.f(1)? == 1
};
let order_hint = if seq.order_hint_bits == 0 {
0
} else {
br.f(u32::from(seq.order_hint_bits))? as u32
};
let primary_ref_frame = if frame_is_intra || error_resilient_mode {
PRIMARY_REF_NONE
} else {
br.f(3)? as u8
};
if decoder_model_info_present(seq) {
return Err(Error::TemporalPointInfoUnsupported);
}
let refresh_frame_flags = if matches!(frame_type, FrameType::Switch)
|| (matches!(frame_type, FrameType::Key) && show_frame)
{
ALL_FRAMES
} else {
br.f(8)? as u8
};
let mut ref_order_hints: Option<[u32; NUM_REF_FRAMES as usize]> = None;
if (!frame_is_intra || refresh_frame_flags != ALL_FRAMES)
&& error_resilient_mode
&& seq.enable_order_hint
{
let mut hints = [0u32; NUM_REF_FRAMES as usize];
for hint in hints.iter_mut() {
*hint = br.f(u32::from(seq.order_hint_bits))? as u32;
}
ref_order_hints = Some(hints);
}
let mut inter_refs: Option<InterFrameRefs> = None;
let mut skip_mode_frame_out: Option<[u8; 2]> = None;
let (
frame_size,
allow_intrabc,
disable_frame_end_update_cdf,
tile_info,
quantization_params,
segmentation_params,
delta_q_params,
delta_lf_params,
loop_filter_params,
cdef_params,
lr_params,
tx_mode,
reference_select,
skip_mode_present,
allow_warped_motion,
reduced_tx_set,
global_motion_params,
film_grain_params,
) = if frame_is_intra {
let fs = parse_frame_size_block(br, seq, frame_size_override_flag)?;
let aib = read_allow_intrabc(br, allow_screen_content_tools, &fs)?;
let dfeuc = if disable_cdf_update {
true
} else {
br.f(1)? == 1
};
let ti = read_tile_info(br, fs.mi_cols, fs.mi_rows, seq.use_128x128_superblock)?;
let qp = read_quantization_params(
br,
seq.color_config.num_planes,
seq.color_config.separate_uv_delta_q,
)?;
let sp = read_segmentation_params(br, primary_ref_frame, None)?;
let dq = read_delta_q_params(br, qp.base_q_idx)?;
let dlf = read_delta_lf_params(br, dq.delta_q_present, aib)?;
let coded_lossless = compute_coded_lossless(&qp, &sp);
let lf =
read_loop_filter_params(br, seq.color_config.num_planes, coded_lossless, aib, None)?;
let cdef = read_cdef_params(
br,
seq.color_config.num_planes,
coded_lossless,
aib,
seq.enable_cdef,
)?;
let all_lossless = coded_lossless && fs.frame_width == fs.upscaled_width;
let lr = read_lr_params(
br,
seq.color_config.num_planes,
seq.color_config.subsampling_x,
seq.color_config.subsampling_y,
seq.use_128x128_superblock,
all_lossless,
aib,
seq.enable_restoration,
)?;
let tx = read_tx_mode(br, coded_lossless)?;
let (rs, smp, awm, rts, gm, fg) =
read_intra_uncompressed_header_tail(br, seq, show_frame, showable_frame, frame_type)?;
(
Some(fs),
aib,
dfeuc,
Some(ti),
Some(qp),
Some(sp),
Some(dq),
Some(dlf),
Some(lf),
Some(cdef),
Some(lr),
Some(tx),
Some(rs),
Some(smp),
Some(awm),
Some(rts),
Some(gm),
Some(fg),
)
} else {
let frame_refs_short_signaling = if seq.enable_order_hint {
br.f(1)? == 1
} else {
false
};
let (short_last, short_gold, mut ref_frame_idx) = if frame_refs_short_signaling {
let last_frame_idx = br.f(3)? as u8;
let gold_frame_idx = br.f(3)? as u8;
let computed = set_frame_refs(
last_frame_idx,
gold_frame_idx,
order_hint,
seq.order_hint_bits,
seq.enable_order_hint,
&ref_info.order_hint,
);
(Some(last_frame_idx), Some(gold_frame_idx), computed)
} else {
(None, None, [0u8; REFS_PER_FRAME])
};
for entry in ref_frame_idx.iter_mut().take(REFS_PER_FRAME) {
if !frame_refs_short_signaling {
*entry = br.f(3)? as u8;
}
if seq.frame_id_numbers_present_flag {
let n = u32::from(seq.delta_frame_id_length_minus_2) + 2;
let _delta_frame_id_minus_1 = br.f(n)?;
}
}
let fs = if frame_size_override_flag && !error_resilient_mode {
read_frame_size_with_refs(br, seq, frame_size_override_flag, &ref_frame_idx, ref_info)?
} else {
parse_frame_size_block(br, seq, frame_size_override_flag)?
};
let allow_high_precision_mv = if force_integer_mv {
false
} else {
br.f(1)? == 1
};
let interpolation_filter = read_interpolation_filter(br)?;
let is_motion_mode_switchable = br.f(1)? == 1;
let use_ref_frame_mvs = if error_resilient_mode || !seq.enable_ref_frame_mvs {
false
} else {
br.f(1)? == 1
};
let dfeuc = if disable_cdf_update {
true
} else {
br.f(1)? == 1
};
let aib = false;
let ti = read_tile_info(br, fs.mi_cols, fs.mi_rows, seq.use_128x128_superblock)?;
let qp = read_quantization_params(
br,
seq.color_config.num_planes,
seq.color_config.separate_uv_delta_q,
)?;
let seg_loaded = if primary_ref_frame == PRIMARY_REF_NONE {
None
} else {
let slot = ref_frame_idx[primary_ref_frame as usize] as usize;
Some((
&ref_info.saved_seg_feature_active[slot],
&ref_info.saved_seg_feature_data[slot],
))
};
let sp = read_segmentation_params(br, primary_ref_frame, seg_loaded)?;
let dq = read_delta_q_params(br, qp.base_q_idx)?;
let dlf = read_delta_lf_params(br, dq.delta_q_present, aib)?;
let coded_lossless = compute_coded_lossless(&qp, &sp);
let lf_prev = if primary_ref_frame == PRIMARY_REF_NONE {
None
} else {
let slot = ref_frame_idx[primary_ref_frame as usize] as usize;
Some((
&ref_info.saved_lf_ref_deltas[slot],
&ref_info.saved_lf_mode_deltas[slot],
))
};
let lf = read_loop_filter_params(
br,
seq.color_config.num_planes,
coded_lossless,
aib,
lf_prev,
)?;
let cdef = read_cdef_params(
br,
seq.color_config.num_planes,
coded_lossless,
aib,
seq.enable_cdef,
)?;
let all_lossless = coded_lossless && fs.frame_width == fs.upscaled_width;
let lr = read_lr_params(
br,
seq.color_config.num_planes,
seq.color_config.subsampling_x,
seq.color_config.subsampling_y,
seq.use_128x128_superblock,
all_lossless,
aib,
seq.enable_restoration,
)?;
let tx = read_tx_mode(br, coded_lossless)?;
let reference_select = br.f(1)? == 1;
let (skip_mode_present, skip_mode_frame) = read_skip_mode_present(
br,
reference_select,
seq.enable_order_hint,
order_hint,
seq.order_hint_bits,
&ref_frame_idx,
ref_info,
)?;
skip_mode_frame_out = if skip_mode_present {
skip_mode_frame
} else {
None
};
let allow_warped_motion = if error_resilient_mode || !seq.enable_warped_motion {
false
} else {
br.f(1)? == 1
};
let reduced_tx_set = br.f(1)? == 1;
let prev_gm = if primary_ref_frame == PRIMARY_REF_NONE {
prev_gm_params_default()
} else {
ref_info.saved_gm_params[ref_frame_idx[primary_ref_frame as usize] as usize]
};
let gm = read_global_motion_params(br, false, allow_high_precision_mv, &prev_gm)?;
let fg = read_film_grain_params(
br,
FilmGrainContext {
film_grain_params_present: seq.film_grain_params_present,
show_frame,
showable_frame,
is_inter_frame: matches!(frame_type, FrameType::Inter),
mono_chrome: seq.color_config.mono_chrome,
subsampling_x: seq.color_config.subsampling_x,
subsampling_y: seq.color_config.subsampling_y,
},
)?;
inter_refs = Some(InterFrameRefs {
frame_refs_short_signaling,
last_frame_idx: short_last,
gold_frame_idx: short_gold,
ref_frame_idx,
allow_high_precision_mv,
interpolation_filter,
is_motion_mode_switchable,
use_ref_frame_mvs,
});
(
Some(fs),
aib,
dfeuc,
Some(ti),
Some(qp),
Some(sp),
Some(dq),
Some(dlf),
Some(lf),
Some(cdef),
Some(lr),
Some(tx),
Some(reference_select),
Some(skip_mode_present),
Some(allow_warped_motion),
Some(reduced_tx_set),
Some(gm),
Some(fg),
)
};
Ok(FrameHeader {
show_existing_frame: false,
frame_to_show_map_idx: None,
display_frame_id: None,
frame_type,
frame_is_intra,
show_frame,
showable_frame,
error_resilient_mode,
disable_cdf_update,
allow_screen_content_tools,
force_integer_mv,
current_frame_id,
frame_size_override_flag,
order_hint,
primary_ref_frame,
refresh_frame_flags,
ref_order_hints,
frame_size,
allow_intrabc,
disable_frame_end_update_cdf,
tile_info,
quantization_params,
segmentation_params,
delta_q_params,
delta_lf_params,
loop_filter_params,
cdef_params,
lr_params,
tx_mode,
reference_select,
skip_mode_present,
skip_mode_frame: skip_mode_frame_out,
allow_warped_motion,
reduced_tx_set,
global_motion_params,
film_grain_params,
inter_refs,
bits_consumed: br.position(),
})
}
fn get_relative_dist(a: i64, b: i64, order_hint_bits: u8, enable_order_hint: bool) -> i64 {
if !enable_order_hint {
return 0;
}
let mut diff = a - b;
let m = 1i64 << (i64::from(order_hint_bits) - 1);
diff = (diff & (m - 1)) - (diff & m);
diff
}
pub(crate) fn set_frame_refs(
last_frame_idx: u8,
gold_frame_idx: u8,
order_hint: u32,
order_hint_bits: u8,
enable_order_hint: bool,
ref_order_hint: &[u32; NUM_REF_FRAMES as usize],
) -> [u8; REFS_PER_FRAME] {
const N: usize = NUM_REF_FRAMES as usize;
let last = last_frame_idx as usize;
let gold = gold_frame_idx as usize;
let mut ref_frame_idx: [i32; REFS_PER_FRAME] = [-1; REFS_PER_FRAME];
ref_frame_idx[0] = last as i32; ref_frame_idx[GOLDEN_FRAME - LAST_FRAME] = gold as i32;
let mut used_frame = [false; N];
used_frame[last] = true;
used_frame[gold] = true;
let cur_frame_hint: i64 = 1 << (i64::from(order_hint_bits) - 1);
let mut shifted = [0i64; N];
for (i, slot) in shifted.iter_mut().enumerate() {
*slot = cur_frame_hint
+ get_relative_dist(
i64::from(ref_order_hint[i]),
i64::from(order_hint),
order_hint_bits,
enable_order_hint,
);
}
let find_latest_backward = |used: &[bool; N]| -> i32 {
let mut r = -1i32;
let mut latest = 0i64;
for i in 0..N {
let h = shifted[i];
if !used[i] && h >= cur_frame_hint && (r < 0 || h >= latest) {
r = i as i32;
latest = h;
}
}
r
};
let find_earliest_backward = |used: &[bool; N]| -> i32 {
let mut r = -1i32;
let mut earliest = 0i64;
for i in 0..N {
let h = shifted[i];
if !used[i] && h >= cur_frame_hint && (r < 0 || h < earliest) {
r = i as i32;
earliest = h;
}
}
r
};
let find_latest_forward = |used: &[bool; N]| -> i32 {
let mut r = -1i32;
let mut latest = 0i64;
for i in 0..N {
let h = shifted[i];
if !used[i] && h < cur_frame_hint && (r < 0 || h >= latest) {
r = i as i32;
latest = h;
}
}
r
};
let r = find_latest_backward(&used_frame);
if r >= 0 {
ref_frame_idx[ALTREF_FRAME - LAST_FRAME] = r;
used_frame[r as usize] = true;
}
let r = find_earliest_backward(&used_frame);
if r >= 0 {
ref_frame_idx[BWDREF_FRAME - LAST_FRAME] = r;
used_frame[r as usize] = true;
}
let r = find_earliest_backward(&used_frame);
if r >= 0 {
ref_frame_idx[ALTREF2_FRAME - LAST_FRAME] = r;
used_frame[r as usize] = true;
}
const REF_FRAME_LIST: [usize; REFS_PER_FRAME - 2] = [
LAST2_FRAME,
LAST3_FRAME,
BWDREF_FRAME,
ALTREF2_FRAME,
ALTREF_FRAME,
];
for &ref_frame in REF_FRAME_LIST.iter() {
if ref_frame_idx[ref_frame - LAST_FRAME] < 0 {
let r = find_latest_forward(&used_frame);
if r >= 0 {
ref_frame_idx[ref_frame - LAST_FRAME] = r;
used_frame[r as usize] = true;
}
}
}
let mut r = -1i32;
let mut earliest = 0i64;
for (i, &h) in shifted.iter().enumerate().take(N) {
if r < 0 || h < earliest {
r = i as i32;
earliest = h;
}
}
for entry in ref_frame_idx.iter_mut() {
if *entry < 0 {
*entry = r;
}
}
let mut out = [0u8; REFS_PER_FRAME];
for (o, v) in out.iter_mut().zip(ref_frame_idx.iter()) {
*o = (*v).max(0) as u8;
}
out
}
fn read_frame_size_with_refs(
br: &mut BitReader<'_>,
seq: &SequenceHeader,
frame_size_override_flag: bool,
ref_frame_idx: &[u8; REFS_PER_FRAME],
ref_info: &RefInfo,
) -> Result<FrameSize, Error> {
let mut found = false;
let mut upscaled_width = 0u32;
let mut frame_height = 0u32;
let mut render_width = 0u32;
let mut render_height = 0u32;
for &slot in ref_frame_idx.iter() {
let found_ref = br.f(1)? == 1;
if found_ref {
let s = slot as usize;
upscaled_width = ref_info.upscaled_width[s];
frame_height = ref_info.frame_height[s];
render_width = ref_info.render_width[s];
render_height = ref_info.render_height[s];
found = true;
break;
}
}
if !found {
return parse_frame_size_block(br, seq, frame_size_override_flag);
}
let (use_superres, coded_denom, superres_denom) = if seq.enable_superres {
let use_superres = br.f(1)? == 1;
if use_superres {
let coded_denom = br.f(SUPERRES_DENOM_BITS)? as u8;
(
true,
coded_denom,
u32::from(coded_denom) + SUPERRES_DENOM_MIN,
)
} else {
(false, 0u8, SUPERRES_NUM)
}
} else {
(false, 0u8, SUPERRES_NUM)
};
let frame_width = (upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom;
let mi_cols = 2 * ((frame_width + 7) >> 3);
let mi_rows = 2 * ((frame_height + 7) >> 3);
Ok(FrameSize {
frame_width,
frame_height,
render_width,
render_height,
superres_denom,
upscaled_width,
mi_cols,
mi_rows,
use_superres,
coded_denom,
render_and_frame_size_different: false,
})
}
#[allow(clippy::too_many_arguments)]
fn read_skip_mode_present(
br: &mut BitReader<'_>,
reference_select: bool,
enable_order_hint: bool,
order_hint: u32,
order_hint_bits: u8,
ref_frame_idx: &[u8; REFS_PER_FRAME],
ref_info: &RefInfo,
) -> Result<(bool, Option<[u8; 2]>), Error> {
if !reference_select || !enable_order_hint {
return Ok((false, None));
}
let rel = |a: u32| -> i64 {
get_relative_dist(
i64::from(a),
i64::from(order_hint),
order_hint_bits,
enable_order_hint,
)
};
let mut forward_idx: i32 = -1;
let mut backward_idx: i32 = -1;
let mut forward_hint: u32 = 0;
let mut backward_hint: u32 = 0;
for (i, &slot) in ref_frame_idx.iter().enumerate() {
let ref_hint = ref_info.order_hint[slot as usize];
if rel(ref_hint) < 0 {
if forward_idx < 0
|| rel_pair(ref_hint, forward_hint, order_hint_bits, enable_order_hint) > 0
{
forward_idx = i as i32;
forward_hint = ref_hint;
}
} else if rel(ref_hint) > 0
&& (backward_idx < 0
|| rel_pair(ref_hint, backward_hint, order_hint_bits, enable_order_hint) < 0)
{
backward_idx = i as i32;
backward_hint = ref_hint;
}
}
let (skip_mode_allowed, skip_mode_frame) = if forward_idx < 0 {
(false, None)
} else if backward_idx >= 0 {
let lo = forward_idx.min(backward_idx) as u8;
let hi = forward_idx.max(backward_idx) as u8;
(true, Some([LAST_FRAME_U8 + lo, LAST_FRAME_U8 + hi]))
} else {
let mut second_forward_idx: i32 = -1;
let mut second_forward_hint: u32 = 0;
for (i, &slot) in ref_frame_idx.iter().enumerate() {
let ref_hint = ref_info.order_hint[slot as usize];
if rel_pair(ref_hint, forward_hint, order_hint_bits, enable_order_hint) < 0
&& (second_forward_idx < 0
|| rel_pair(
ref_hint,
second_forward_hint,
order_hint_bits,
enable_order_hint,
) > 0)
{
second_forward_idx = i as i32;
second_forward_hint = ref_hint;
}
}
if second_forward_idx >= 0 {
let lo = forward_idx.min(second_forward_idx) as u8;
let hi = forward_idx.max(second_forward_idx) as u8;
(true, Some([LAST_FRAME_U8 + lo, LAST_FRAME_U8 + hi]))
} else {
(false, None)
}
};
if skip_mode_allowed {
let present = br.f(1)? == 1;
Ok((present, skip_mode_frame))
} else {
Ok((false, skip_mode_frame))
}
}
const LAST_FRAME_U8: u8 = crate::uncompressed_header_tail::LAST_FRAME as u8;
fn rel_pair(a: u32, b: u32, order_hint_bits: u8, enable_order_hint: bool) -> i64 {
get_relative_dist(
i64::from(a),
i64::from(b),
order_hint_bits,
enable_order_hint,
)
}
fn compute_coded_lossless(qp: &QuantizationParams, sp: &SegmentationParams) -> bool {
let deltas_all_zero = qp.delta_q_y_dc == 0
&& qp.delta_q_u_dc == 0
&& qp.delta_q_u_ac == 0
&& qp.delta_q_v_dc == 0
&& qp.delta_q_v_ac == 0;
if !deltas_all_zero {
return false;
}
for segment_id in 0..MAX_SEGMENTS {
let qindex = if sp.enabled && sp.segment_feature_active[segment_id][SEG_LVL_ALT_Q] {
let data = i32::from(sp.segment_feature_data[segment_id][SEG_LVL_ALT_Q]);
(i32::from(qp.base_q_idx) + data).clamp(0, 255)
} else {
i32::from(qp.base_q_idx)
};
if qindex != 0 {
return false;
}
}
true
}
#[allow(clippy::type_complexity)]
fn read_intra_uncompressed_header_tail(
br: &mut BitReader<'_>,
seq: &SequenceHeader,
show_frame: bool,
showable_frame: bool,
frame_type: FrameType,
) -> Result<(bool, bool, bool, bool, GlobalMotionParams, FilmGrainParams), Error> {
let reference_select = false;
let skip_mode_present = false;
let allow_warped_motion = false;
let reduced_tx_set = br.f(1)? == 1;
let global_motion = read_global_motion_params(br, true, false, &prev_gm_params_default())?;
let film_grain = read_film_grain_params(
br,
FilmGrainContext {
film_grain_params_present: seq.film_grain_params_present,
show_frame,
showable_frame,
is_inter_frame: matches!(frame_type, FrameType::Inter),
mono_chrome: seq.color_config.mono_chrome,
subsampling_x: seq.color_config.subsampling_x,
subsampling_y: seq.color_config.subsampling_y,
},
)?;
Ok((
reference_select,
skip_mode_present,
allow_warped_motion,
reduced_tx_set,
global_motion,
film_grain,
))
}
fn read_allow_intrabc(
br: &mut BitReader<'_>,
allow_screen_content_tools: bool,
fs: &FrameSize,
) -> Result<bool, Error> {
if allow_screen_content_tools && fs.upscaled_width == fs.frame_width {
Ok(br.f(1)? == 1)
} else {
Ok(false)
}
}
fn parse_frame_size_block(
br: &mut BitReader<'_>,
seq: &SequenceHeader,
frame_size_override_flag: bool,
) -> Result<FrameSize, Error> {
let (frame_width_initial, frame_height) = if frame_size_override_flag {
let n_w = u32::from(seq.frame_width_bits_minus_1) + 1;
let frame_width_minus_1 = br.f(n_w)? as u32;
let n_h = u32::from(seq.frame_height_bits_minus_1) + 1;
let frame_height_minus_1 = br.f(n_h)? as u32;
(frame_width_minus_1 + 1, frame_height_minus_1 + 1)
} else {
(
seq.max_frame_width_minus_1 + 1,
seq.max_frame_height_minus_1 + 1,
)
};
let (use_superres, coded_denom, superres_denom) = if seq.enable_superres {
let use_superres = br.f(1)? == 1;
if use_superres {
let coded_denom = br.f(SUPERRES_DENOM_BITS)? as u8;
(
true,
coded_denom,
u32::from(coded_denom) + SUPERRES_DENOM_MIN,
)
} else {
(false, 0u8, SUPERRES_NUM)
}
} else {
(false, 0u8, SUPERRES_NUM)
};
let upscaled_width = frame_width_initial;
let frame_width = (upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom;
let mi_cols = 2 * ((frame_width + 7) >> 3);
let mi_rows = 2 * ((frame_height + 7) >> 3);
let render_and_frame_size_different = br.f(1)? == 1;
let (render_width, render_height) = if render_and_frame_size_different {
let rw = (br.f(16)? as u32) + 1;
let rh = (br.f(16)? as u32) + 1;
(rw, rh)
} else {
(upscaled_width, frame_height)
};
Ok(FrameSize {
frame_width,
frame_height,
render_width,
render_height,
superres_denom,
upscaled_width,
mi_cols,
mi_rows,
use_superres,
coded_denom,
render_and_frame_size_different,
})
}
fn read_allow_scc(br: &mut BitReader<'_>, seq: &SequenceHeader) -> Result<bool, Error> {
if seq.seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS {
Ok(br.f(1)? == 1)
} else {
Ok(seq.seq_force_screen_content_tools != 0)
}
}
fn consume_force_integer_mv_if_present(
br: &mut BitReader<'_>,
seq: &SequenceHeader,
allow_screen_content_tools: bool,
) -> Result<bool, Error> {
if allow_screen_content_tools {
if seq.seq_force_integer_mv == SELECT_INTEGER_MV {
Ok(br.f(1)? == 1)
} else {
Ok(seq.seq_force_integer_mv != 0)
}
} else {
Ok(false)
}
}
fn decoder_model_info_present(seq: &SequenceHeader) -> bool {
seq.decoder_model_info_present_flag
}
fn equal_picture_interval(seq: &SequenceHeader) -> bool {
seq.timing_info
.map(|t| t.equal_picture_interval)
.unwrap_or(true)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sequence_header::parse_sequence_header;
fn tiny_seq() -> SequenceHeader {
let seq_payload: &[u8] = &[0x00, 0x00, 0x00, 0x01, 0x9f, 0xfb, 0xff, 0xf3, 0x00, 0x80];
parse_sequence_header(seq_payload).expect("seq header parses")
}
fn show_existing_seq() -> SequenceHeader {
let seq_payload: &[u8] = &[0x00, 0x00, 0x00, 0x02, 0xaf, 0xff, 0x9b, 0x5f, 0x30, 0x08];
parse_sequence_header(seq_payload).expect("show-existing seq header parses")
}
fn screen_content_seq() -> SequenceHeader {
let seq_payload: &[u8] = &[0x18, 0x1d, 0xbf, 0xff, 0xf2, 0x01];
parse_sequence_header(seq_payload).expect("screen-content seq header parses")
}
fn super_resolution_seq() -> SequenceHeader {
let seq_payload: &[u8] = &[0x18, 0x19, 0x7f, 0xff, 0xf8, 0x04];
parse_sequence_header(seq_payload).expect("super-res seq header parses")
}
const TINY_FRAME_PAYLOAD: &[u8] = &[
0x10, 0x00, 0xbc, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x78, 0x9d, 0x76, 0x2f, 0x67,
0x6c, 0xc7, 0xee, 0x51, 0x80,
];
#[test]
fn parses_tiny_key_frame_prefix() {
let seq = tiny_seq();
let fh = parse_frame_header(TINY_FRAME_PAYLOAD, &seq).expect("frame header parses");
assert!(!fh.show_existing_frame);
assert_eq!(fh.frame_type, FrameType::Key);
assert!(fh.frame_is_intra);
assert!(fh.show_frame);
assert!(!fh.showable_frame, "KEY + show_frame => showable=0");
assert!(
fh.error_resilient_mode,
"KEY + show_frame => error_resilient forced to 1"
);
assert!(!fh.disable_cdf_update);
assert!(!fh.allow_screen_content_tools);
assert!(fh.force_integer_mv);
assert_eq!(fh.current_frame_id, 0);
assert!(!fh.frame_size_override_flag);
assert_eq!(fh.order_hint, 0);
assert_eq!(fh.primary_ref_frame, PRIMARY_REF_NONE);
assert_eq!(fh.refresh_frame_flags, 0xff);
assert_eq!(fh.bits_consumed, 72);
let qp = fh
.quantization_params
.as_ref()
.expect("intra frame produces quantization_params");
assert_eq!(qp.base_q_idx, 120, "trace base_q_idx=120");
assert_eq!(qp.delta_q_y_dc, 0);
assert_eq!(qp.delta_q_u_dc, 0);
assert_eq!(qp.delta_q_u_ac, 0);
assert!(!qp.using_qmatrix);
let sp = fh
.segmentation_params
.as_ref()
.expect("intra frame produces segmentation_params");
assert!(!sp.enabled);
assert!(!sp.update_map);
assert!(!sp.temporal_update);
assert!(!sp.update_data);
assert!(!sp.seg_id_pre_skip);
assert_eq!(sp.last_active_seg_id, 0);
let dq = fh
.delta_q_params
.as_ref()
.expect("intra frame produces delta_q_params");
assert!(!dq.delta_q_present);
assert_eq!(dq.delta_q_res, 0);
let dlf = fh
.delta_lf_params
.as_ref()
.expect("intra frame produces delta_lf_params");
assert!(!dlf.delta_lf_present);
assert_eq!(dlf.delta_lf_res, 0);
assert!(!dlf.delta_lf_multi);
let lf = fh
.loop_filter_params
.as_ref()
.expect("intra frame produces loop_filter_params");
assert!(!lf.short_circuited);
assert_eq!(lf.loop_filter_level, [0, 0, 0, 0]);
assert_eq!(lf.loop_filter_sharpness, 0);
assert!(lf.loop_filter_delta_enabled);
assert!(!lf.loop_filter_delta_update);
let cdef = fh
.cdef_params
.as_ref()
.expect("intra frame produces cdef_params");
assert!(!cdef.short_circuited);
assert_eq!(cdef.cdef_damping, 4);
assert_eq!(cdef.cdef_bits, 0);
assert_eq!(cdef.cdef_y_pri_strength[0], 0);
assert_eq!(cdef.cdef_y_sec_strength[0], 0);
assert_eq!(cdef.cdef_uv_pri_strength[0], 0);
assert_eq!(cdef.cdef_uv_sec_strength[0], 0);
let tx = fh.tx_mode.expect("intra frame produces tx_mode");
assert_eq!(tx, TxMode::TxModeLargest);
assert_eq!(fh.reference_select, Some(false));
assert_eq!(fh.skip_mode_present, Some(false));
assert_eq!(fh.allow_warped_motion, Some(false));
assert_eq!(fh.reduced_tx_set, Some(false));
let gm = fh
.global_motion_params
.as_ref()
.expect("intra frame produces global_motion_params");
assert!(gm.short_circuited);
assert_eq!(gm, &GlobalMotionParams::identity());
let fg = fh
.film_grain_params
.as_ref()
.expect("intra frame produces film_grain_params");
assert!(!fg.apply_grain);
assert_eq!(fg, &FilmGrainParams::reset());
let ti = fh.tile_info.as_ref().expect("intra frame has tile_info");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.context_update_tile_id, 0);
assert!(!fh.allow_intrabc);
assert!(!fh.disable_frame_end_update_cdf);
let fs = fh.frame_size.expect("intra frame produces frame_size");
assert_eq!(fs.frame_width, 16);
assert_eq!(fs.frame_height, 16);
assert_eq!(fs.upscaled_width, 16);
assert_eq!(fs.render_width, 16);
assert_eq!(fs.render_height, 16);
assert_eq!(fs.superres_denom, SUPERRES_NUM);
assert_eq!(fs.mi_cols, 4);
assert_eq!(fs.mi_rows, 4);
assert!(!fs.use_superres);
assert_eq!(fs.coded_denom, 0);
assert!(!fs.render_and_frame_size_different);
assert!(!fs.is_super_resolved());
}
const SHOW_EXISTING_KEY_PAYLOAD: &[u8] = &[
0x01, 0x00, 0x7f, 0x89, 0x10, 0x02, 0x08, 0x10, 0x82, 0x00, 0x0a, 0x02, 0xdc, 0x85, 0x28,
0xf5,
];
#[test]
fn parses_show_existing_underlying_key_frame() {
let seq = show_existing_seq();
let fh = parse_frame_header(SHOW_EXISTING_KEY_PAYLOAD, &seq).expect("frame header parses");
assert!(!fh.show_existing_frame);
assert_eq!(fh.frame_type, FrameType::Key);
assert!(!fh.show_frame);
assert!(!fh.showable_frame);
assert!(!fh.error_resilient_mode, "KEY + show_frame=0 reads er_mode");
assert!(fh.allow_screen_content_tools);
assert!(fh.force_integer_mv);
assert_eq!(fh.order_hint, 0);
assert_eq!(fh.primary_ref_frame, PRIMARY_REF_NONE);
assert_eq!(fh.refresh_frame_flags, 0xff);
let fs = fh.frame_size.expect("intra frame produces frame_size");
assert_eq!(fs.frame_width, 64);
assert_eq!(fs.frame_height, 64);
assert_eq!(fs.upscaled_width, 64);
assert_eq!(fs.render_width, 64);
assert_eq!(fs.render_height, 64);
assert_eq!(fs.superres_denom, SUPERRES_NUM);
assert_eq!(fs.mi_cols, 16);
assert_eq!(fs.mi_rows, 16);
assert!(!fs.use_superres);
assert!(!fh.allow_intrabc);
let ti = fh.tile_info.as_ref().expect("intra frame has tile_info");
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
}
#[test]
fn synthetic_show_existing_frame_replay() {
let payload = [0b1101_0000u8];
let seq = tiny_seq();
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert!(fh.show_existing_frame);
assert_eq!(fh.frame_to_show_map_idx, Some(5));
assert!(fh.display_frame_id.is_none());
assert_eq!(fh.refresh_frame_flags, 0);
assert_eq!(fh.bits_consumed, 4);
}
#[test]
fn reduced_still_picture_path() {
let seq = screen_content_seq();
assert!(seq.reduced_still_picture_header);
assert_eq!(
seq.seq_force_screen_content_tools, SELECT_SCREEN_CONTENT_TOOLS,
"reduced still forces SELECT"
);
assert!(!seq.enable_superres);
let payload = [0x44u8, 0x00, 0x00];
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert_eq!(fh.frame_type, FrameType::Key);
assert!(fh.frame_is_intra);
assert!(fh.show_frame);
assert!(!fh.showable_frame);
assert!(fh.error_resilient_mode);
assert!(!fh.disable_cdf_update);
assert!(fh.allow_screen_content_tools);
assert!(fh.force_integer_mv);
assert_eq!(fh.refresh_frame_flags, 0xff);
assert_eq!(fh.primary_ref_frame, PRIMARY_REF_NONE);
assert!(!fh.allow_intrabc);
assert!(!fh.reduced_tx_set.expect("intra has reduced_tx_set"));
assert!(
fh.global_motion_params
.as_ref()
.expect("intra has global_motion")
.short_circuited
);
assert!(
!fh.film_grain_params
.as_ref()
.expect("intra has film_grain")
.apply_grain
);
assert_eq!(fh.bits_consumed, 21);
let qp = fh.quantization_params.as_ref().expect("intra has qp");
assert_eq!(qp.base_q_idx, 0);
assert!(!qp.using_qmatrix);
let sp = fh.segmentation_params.as_ref().expect("intra has sp");
assert!(!sp.enabled);
let fs = fh.frame_size.expect("reduced-still produces frame_size");
assert_eq!(fs.frame_width, 256);
assert_eq!(fs.frame_height, 128);
assert_eq!(fs.upscaled_width, 256);
assert_eq!(fs.mi_cols, 64);
assert_eq!(fs.mi_rows, 32);
assert!(!fs.use_superres);
assert!(!fs.render_and_frame_size_different);
assert_eq!(fs.render_width, 256);
assert_eq!(fs.render_height, 128);
let ti = fh.tile_info.as_ref().expect("intra frame has tile_info");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
}
#[test]
fn reduced_still_without_select_force_int_mv() {
let seq = super_resolution_seq();
assert!(seq.reduced_still_picture_header);
assert!(seq.enable_superres);
let payload = [0x42u8, 0x00, 0x00];
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert!(fh.allow_screen_content_tools);
assert!(fh.force_integer_mv);
let fs = fh.frame_size.expect("reduced-still produces frame_size");
assert_eq!(fs.frame_width, 128);
assert_eq!(fs.upscaled_width, 128);
assert_eq!(fs.frame_height, 64);
assert!(!fs.use_superres);
assert_eq!(fs.superres_denom, SUPERRES_NUM);
assert!(!fh.allow_intrabc);
assert!(!fh.reduced_tx_set.expect("intra has reduced_tx_set"));
assert_eq!(fh.bits_consumed, 21);
let sp = fh.segmentation_params.as_ref().expect("intra has sp");
assert!(!sp.enabled);
let ti = fh.tile_info.as_ref().expect("intra frame has tile_info");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
}
#[test]
fn reduced_still_with_use_superres() {
let payload = [0x56u8, 0x80u8, 0x00u8, 0x00u8];
let seq = super_resolution_seq();
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert_eq!(fh.bits_consumed, 29);
let fs = fh.frame_size.expect("reduced-still produces frame_size");
assert!(fs.use_superres);
assert_eq!(fs.coded_denom, 3);
assert_eq!(fs.superres_denom, 12);
assert_eq!(fs.upscaled_width, 128);
assert_eq!(fs.frame_width, 85);
assert_eq!(fs.frame_height, 64);
assert_eq!(fs.mi_cols, 22);
assert_eq!(fs.mi_rows, 16);
assert_eq!(fs.render_width, 128);
assert_eq!(fs.render_height, 64);
assert!(fs.is_super_resolved());
assert!(!fh.allow_intrabc);
let ti = fh.tile_info.as_ref().expect("intra frame has tile_info");
assert!(ti.uniform_tile_spacing_flag);
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
assert_eq!(fh.tx_mode, Some(TxMode::Only4x4));
}
#[test]
fn explicit_render_and_frame_size_different() {
let seq = tiny_seq();
let bits: &[u8] = &[
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
let mut payload = vec![0u8; bits.len().div_ceil(8)];
for (i, &b) in bits.iter().enumerate() {
if b != 0 {
payload[i / 8] |= 1 << (7 - (i % 8));
}
}
let fh = parse_frame_header(&payload, &seq).expect("decodes");
let fs = fh.frame_size.expect("intra frame produces frame_size");
assert!(fs.render_and_frame_size_different);
assert_eq!(fs.render_width, 40);
assert_eq!(fs.render_height, 30);
assert_eq!(fs.frame_width, 16);
assert_eq!(fs.frame_height, 16);
assert!(!fh.reduced_tx_set.expect("intra has reduced_tx_set"));
assert_eq!(fh.bits_consumed, bits.len());
}
#[test]
fn frame_size_override_reads_explicit_dims() {
let seq = tiny_seq();
let n_w = u32::from(seq.frame_width_bits_minus_1) + 1;
let n_h = u32::from(seq.frame_height_bits_minus_1) + 1;
assert!((1..=16).contains(&n_w));
assert!((1..=16).contains(&n_h));
let target_w = 12u32;
let target_h = 10u32;
let w_minus_1 = target_w - 1; let h_minus_1 = target_h - 1; let mut bits = vec![
0u8, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
];
for i in (0..n_w).rev() {
bits.push(((w_minus_1 >> i) & 1) as u8);
}
for i in (0..n_h).rev() {
bits.push(((h_minus_1 >> i) & 1) as u8);
}
bits.push(0);
bits.push(0);
bits.push(1);
bits.resize(bits.len() + 13, 0);
let mut payload = vec![0u8; bits.len().div_ceil(8)];
for (i, &b) in bits.iter().enumerate() {
if b != 0 {
payload[i / 8] |= 1 << (7 - (i % 8));
}
}
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert!(fh.frame_size_override_flag);
let fs = fh.frame_size.expect("intra frame produces frame_size");
assert_eq!(fs.frame_width, target_w);
assert_eq!(fs.frame_height, target_h);
assert_eq!(fs.render_width, 12);
assert_eq!(fs.render_height, 10);
let ti = fh.tile_info.as_ref().expect("intra has tile_info");
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
}
#[test]
fn frame_type_roundtrip() {
for raw in 0u8..4u8 {
let ft = FrameType::from_raw(raw);
assert_eq!(ft.as_raw(), raw);
}
assert!(FrameType::Key.is_intra());
assert!(!FrameType::Inter.is_intra());
assert!(FrameType::IntraOnly.is_intra());
assert!(!FrameType::Switch.is_intra());
}
#[test]
fn unexpected_end_on_truncated_payload() {
let seq = tiny_seq();
let payload: [u8; 0] = [];
let err = parse_frame_header(&payload, &seq).expect_err("must error");
assert_eq!(err, Error::UnexpectedEnd);
}
#[test]
fn allow_intrabc_read_when_gated() {
let seq = screen_content_seq();
let payload = [0x4Au8, 0x00, 0x00];
let fh = parse_frame_header(&payload, &seq).expect("decodes");
assert!(fh.allow_intrabc);
assert!(fh.disable_frame_end_update_cdf, "reduced-still derives 1");
let ti = fh.tile_info.as_ref().expect("intra has tile_info");
assert_eq!(ti.tile_cols, 1);
assert_eq!(ti.tile_rows, 1);
}
#[test]
fn context_update_tile_id_read_when_log2_nonzero() {
let seq = screen_content_seq();
let payload = [0x47u8, 0x80u8, 0x00u8];
let fh = parse_frame_header(&payload, &seq).expect("decodes");
let ti = fh.tile_info.as_ref().expect("intra has tile_info");
assert_eq!(ti.tile_cols, 2);
assert_eq!(ti.tile_rows, 1);
assert_eq!(ti.tile_cols_log2, 1);
assert_eq!(ti.context_update_tile_id, 1);
assert_eq!(ti.tile_size_bytes, 3);
}
#[test]
fn segmentation_streaming_synthetic_alt_q_active() {
use crate::uncompressed_header_tail::{
MAX_SEGMENTS, SEG_LVL_ALT_Q, SEG_LVL_MAX, SEG_LVL_REF_FRAME,
};
let seq = screen_content_seq();
let mut bits: Vec<u8> = vec![
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
bits.push(1); bits.push(1); for i in (0..9).rev() {
bits.push(((389u32 >> i) & 1) as u8);
}
let tail_zeros = (SEG_LVL_MAX - 1) + (MAX_SEGMENTS - 1) * SEG_LVL_MAX;
bits.resize(bits.len() + tail_zeros, 0);
let mut payload = vec![0u8; bits.len().div_ceil(8)];
for (i, &b) in bits.iter().enumerate() {
if b != 0 {
payload[i / 8] |= 1 << (7 - (i % 8));
}
}
let fh = parse_frame_header(&payload, &seq).expect("decodes");
let sp = fh.segmentation_params.as_ref().expect("intra has sp");
assert!(sp.enabled);
assert!(sp.update_map);
assert!(!sp.temporal_update);
assert!(sp.update_data);
assert!(sp.segment_feature_active[0][SEG_LVL_ALT_Q]);
assert_eq!(sp.segment_feature_data[0][SEG_LVL_ALT_Q], -123);
for i in 0..MAX_SEGMENTS {
for j in 0..SEG_LVL_MAX {
if (i, j) != (0, SEG_LVL_ALT_Q) {
assert!(
!sp.segment_feature_active[i][j],
"feature ({i},{j}) must be inactive"
);
assert_eq!(sp.segment_feature_data[i][j], 0);
}
}
}
assert!(!sp.seg_id_pre_skip);
assert_eq!(SEG_LVL_ALT_Q, 0);
assert_eq!(SEG_LVL_REF_FRAME, 5);
assert_eq!(sp.last_active_seg_id, 0);
let lf = fh
.loop_filter_params
.as_ref()
.expect("intra has loop_filter_params");
assert!(lf.short_circuited);
assert!(!lf.loop_filter_delta_enabled);
assert_eq!(lf.loop_filter_level, [0, 0, 0, 0]);
assert_eq!(fh.tx_mode, Some(TxMode::Only4x4));
}
#[test]
fn coded_lossless_true_when_base_q_zero_no_deltas_seg_off() {
let qp = QuantizationParams {
base_q_idx: 0,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
let sp = SegmentationParams::disabled();
assert!(compute_coded_lossless(&qp, &sp));
}
#[test]
fn coded_lossless_false_when_base_q_nonzero() {
let qp = QuantizationParams {
base_q_idx: 120,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
let sp = SegmentationParams::disabled();
assert!(!compute_coded_lossless(&qp, &sp));
}
#[test]
fn coded_lossless_false_when_any_delta_q_nonzero() {
let qp = QuantizationParams {
base_q_idx: 0,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: -3,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
let sp = SegmentationParams::disabled();
assert!(!compute_coded_lossless(&qp, &sp));
}
#[test]
fn coded_lossless_alt_q_clamps_to_zero() {
let qp = QuantizationParams {
base_q_idx: 120,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
let mut sp = SegmentationParams::disabled();
sp.enabled = true;
sp.segment_feature_active[0][SEG_LVL_ALT_Q] = true;
sp.segment_feature_data[0][SEG_LVL_ALT_Q] = -200;
assert!(!compute_coded_lossless(&qp, &sp));
for seg in sp.segment_feature_active.iter_mut() {
seg[SEG_LVL_ALT_Q] = true;
}
for seg in sp.segment_feature_data.iter_mut() {
seg[SEG_LVL_ALT_Q] = -200;
}
assert!(compute_coded_lossless(&qp, &sp));
}
#[test]
fn coded_lossless_alt_q_ignored_when_segmentation_disabled() {
let qp = QuantizationParams {
base_q_idx: 0,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
let mut sp = SegmentationParams::disabled();
sp.segment_feature_active[0][SEG_LVL_ALT_Q] = true;
sp.segment_feature_data[0][SEG_LVL_ALT_Q] = 50;
assert!(compute_coded_lossless(&qp, &sp));
}
#[test]
fn loop_filter_streaming_full_path_with_levels() {
let seq = screen_content_seq();
let mut bits: Vec<u8> = vec![
0, 1, 0, 0, 0, 1, 0, ];
for i in (0..8).rev() {
bits.push(((40u32 >> i) & 1) as u8);
}
bits.extend_from_slice(&[0, 0, 0, 0]); bits.push(0);
bits.push(0);
for i in (0..6).rev() {
bits.push(((9u32 >> i) & 1) as u8);
}
bits.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
for i in (0..6).rev() {
bits.push(((16u32 >> i) & 1) as u8);
}
for i in (0..6).rev() {
bits.push(((7u32 >> i) & 1) as u8);
}
for i in (0..3).rev() {
bits.push(((2u32 >> i) & 1) as u8);
}
bits.push(0);
let mut payload = vec![0u8; bits.len().div_ceil(8) + 2];
for (i, &b) in bits.iter().enumerate() {
if b != 0 {
payload[i / 8] |= 1 << (7 - (i % 8));
}
}
let fh = parse_frame_header(&payload, &seq).expect("decodes");
let qp = fh.quantization_params.as_ref().expect("intra has qp");
assert_eq!(qp.base_q_idx, 40);
let lf = fh
.loop_filter_params
.as_ref()
.expect("intra has loop_filter_params");
assert!(!lf.short_circuited);
assert_eq!(lf.loop_filter_level[0], 9);
assert_eq!(lf.loop_filter_level[1], 0);
assert_eq!(lf.loop_filter_level[2], 16);
assert_eq!(lf.loop_filter_level[3], 7);
assert_eq!(lf.loop_filter_sharpness, 2);
assert!(!lf.loop_filter_delta_enabled);
assert_eq!(fh.tx_mode, Some(TxMode::TxModeLargest));
assert_eq!(fh.reduced_tx_set, Some(false));
assert!(
fh.global_motion_params
.as_ref()
.expect("intra has global_motion")
.short_circuited
);
assert!(
!fh.film_grain_params
.as_ref()
.expect("intra has film_grain")
.apply_grain
);
}
#[test]
fn get_relative_dist_sign_extends() {
assert_eq!(get_relative_dist(2, 1, 7, true), 1);
assert_eq!(get_relative_dist(1, 2, 7, true), -1);
assert_eq!(get_relative_dist(0, 127, 7, true), 1);
assert_eq!(get_relative_dist(50, 1, 7, false), 0);
}
#[test]
fn set_frame_refs_all_forward() {
let ref_order_hint = [4u32, 5, 6, 7, 0, 0, 0, 0];
let idx = set_frame_refs(3, 0, 8, 7, true, &ref_order_hint);
assert_eq!(idx[LAST_FRAME - LAST_FRAME], 3, "LAST = last_frame_idx");
assert_eq!(idx[GOLDEN_FRAME - LAST_FRAME], 0, "GOLDEN = gold_frame_idx");
for (i, &v) in idx.iter().enumerate() {
assert!(
(v as usize) < NUM_REF_FRAMES as usize,
"idx[{i}]={v} in range"
);
}
}
#[test]
fn set_frame_refs_backward_ref_picked_for_altref() {
let ref_order_hint = [2u32, 3, 6, 8, 0, 0, 0, 0];
let idx = set_frame_refs(1, 0, 4, 7, true, &ref_order_hint);
assert_eq!(idx[LAST_FRAME - LAST_FRAME], 1, "LAST = last_frame_idx=1");
assert_eq!(
idx[GOLDEN_FRAME - LAST_FRAME],
0,
"GOLDEN = gold_frame_idx=0"
);
assert_eq!(
idx[ALTREF_FRAME - LAST_FRAME],
3,
"ALTREF = latest backward (slot 3)"
);
assert_eq!(
idx[BWDREF_FRAME - LAST_FRAME],
2,
"BWDREF = earliest backward (slot 2)"
);
}
}