#![forbid(unsafe_code)]
use crate::DecodeError;
use mediaway_sw::h264::BitReader;
use super::bits::su;
use super::sequence_header::SequenceHeader;
use super::tile_info::{self, TileInfo};
const SELECT_VALUE: u32 = 2;
const TOTAL_REFS_PER_FRAME: usize = 8;
const DEFAULT_REF_DELTAS: [i8; TOTAL_REFS_PER_FRAME] = [1, 0, 0, 0, -1, 0, -1, -1];
const DEFAULT_MODE_DELTAS: [i8; 2] = [0, 0];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct QuantizationParams {
pub(super) base_q_idx: u8,
pub(super) delta_q_y_dc: i8,
pub(super) delta_q_u_dc: i8,
pub(super) delta_q_u_ac: i8,
pub(super) delta_q_v_dc: i8,
pub(super) delta_q_v_ac: i8,
pub(super) using_qmatrix: bool,
pub(super) qm_y: u8,
pub(super) qm_u: u8,
pub(super) qm_v: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct LoopFilterParams {
pub(super) level: [u8; 2],
pub(super) level_u: u8,
pub(super) level_v: u8,
pub(super) sharpness: u8,
pub(super) delta_enabled: bool,
pub(super) delta_update: bool,
pub(super) ref_deltas: [i8; TOTAL_REFS_PER_FRAME],
pub(super) mode_deltas: [i8; 2],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
clippy::struct_excessive_bools,
reason = "each bool names one AV1 uncompressed_header() syntax element or spec-derived \
flag; a state machine would obscure the 1:1 spec mapping this crate relies on \
for review, same precedent as this crate's H.264 Pps"
)]
pub(super) struct FrameHeader {
pub(super) frame_width_minus1: u16,
pub(super) frame_height_minus1: u16,
pub(super) order_hint: u8,
pub(super) disable_cdf_update: bool,
pub(super) disable_frame_end_update_cdf: bool,
pub(super) allow_screen_content_tools: bool,
pub(super) allow_intrabc: bool,
pub(super) tile_info: TileInfo,
pub(super) quantization: QuantizationParams,
pub(super) delta_q_present: bool,
pub(super) delta_q_res: u8,
pub(super) delta_lf_present: bool,
pub(super) delta_lf_res: u8,
pub(super) delta_lf_multi: bool,
pub(super) coded_lossless: bool,
pub(super) loop_filter: LoopFilterParams,
pub(super) tx_mode: u32,
pub(super) reduced_tx_set: bool,
pub(super) bits_consumed: usize,
}
fn read_delta_q(r: &mut BitReader<'_>) -> Result<i8, DecodeError> {
let map_err = |_| DecodeError::InvalidInput;
let delta_coded = r.read_bit().map_err(map_err)? != 0;
if !delta_coded {
return Ok(0);
}
let value = su(r, 7)?;
i8::try_from(value).map_err(|_| DecodeError::InvalidInput)
}
#[allow(
clippy::similar_names,
reason = "delta_q_{y,u,v}_{dc,ac} are the AV1 spec's own quantization_params() names \
(§5.9.12) — a 1:1 spec mapping this crate relies on for review, same precedent \
as this crate's H.264 pic_init_qp_minus26/pic_init_qs_minus26 allow"
)]
fn parse_quantization_params(
r: &mut BitReader<'_>,
separate_uv_delta_q: bool,
) -> Result<QuantizationParams, DecodeError> {
let map_err = |_| DecodeError::InvalidInput;
let base_q_idx = u8::try_from(r.read_bits(8).map_err(map_err)?).unwrap_or(0);
let delta_q_y_dc = read_delta_q(r)?;
let diff_uv_delta = separate_uv_delta_q && r.read_bit().map_err(map_err)? != 0;
let delta_q_u_dc = read_delta_q(r)?;
let delta_q_u_ac = read_delta_q(r)?;
let (delta_q_v_dc, delta_q_v_ac) = if diff_uv_delta {
(read_delta_q(r)?, read_delta_q(r)?)
} else {
(delta_q_u_dc, delta_q_u_ac)
};
let using_qmatrix = r.read_bit().map_err(map_err)? != 0;
let (qm_y, qm_u, qm_v) = if using_qmatrix {
let qm_y = u8::try_from(r.read_bits(4).map_err(map_err)?).unwrap_or(0);
let qm_u = u8::try_from(r.read_bits(4).map_err(map_err)?).unwrap_or(0);
let qm_v = if separate_uv_delta_q {
u8::try_from(r.read_bits(4).map_err(map_err)?).unwrap_or(0)
} else {
qm_u
};
(qm_y, qm_u, qm_v)
} else {
(0, 0, 0)
};
Ok(QuantizationParams {
base_q_idx,
delta_q_y_dc,
delta_q_u_dc,
delta_q_u_ac,
delta_q_v_dc,
delta_q_v_ac,
using_qmatrix,
qm_y,
qm_u,
qm_v,
})
}
const fn is_coded_lossless(q: &QuantizationParams) -> bool {
q.base_q_idx == 0
&& q.delta_q_y_dc == 0
&& q.delta_q_u_ac == 0
&& q.delta_q_u_dc == 0
&& q.delta_q_v_ac == 0
&& q.delta_q_v_dc == 0
}
fn parse_loop_filter_params(
r: &mut BitReader<'_>,
coded_lossless: bool,
allow_intrabc: bool,
) -> Result<LoopFilterParams, DecodeError> {
let map_err = |_| DecodeError::InvalidInput;
if coded_lossless || allow_intrabc {
return Ok(LoopFilterParams {
level: [0, 0],
level_u: 0,
level_v: 0,
sharpness: 0,
delta_enabled: false,
delta_update: false,
ref_deltas: DEFAULT_REF_DELTAS,
mode_deltas: DEFAULT_MODE_DELTAS,
});
}
let level0 = u8::try_from(r.read_bits(6).map_err(map_err)?).unwrap_or(0);
let level1 = u8::try_from(r.read_bits(6).map_err(map_err)?).unwrap_or(0);
let (level_u, level_v) = if level0 != 0 || level1 != 0 {
(
u8::try_from(r.read_bits(6).map_err(map_err)?).unwrap_or(0),
u8::try_from(r.read_bits(6).map_err(map_err)?).unwrap_or(0),
)
} else {
(0, 0)
};
let sharpness = u8::try_from(r.read_bits(3).map_err(map_err)?).unwrap_or(0);
let delta_enabled = r.read_bit().map_err(map_err)? != 0;
let mut ref_deltas = DEFAULT_REF_DELTAS;
let mut mode_deltas = DEFAULT_MODE_DELTAS;
let delta_update = delta_enabled && r.read_bit().map_err(map_err)? != 0;
if delta_update {
for slot in &mut ref_deltas {
if r.read_bit().map_err(map_err)? != 0 {
let value = su(r, 7)?;
*slot = i8::try_from(value).map_err(|_| DecodeError::InvalidInput)?;
}
}
for slot in &mut mode_deltas {
if r.read_bit().map_err(map_err)? != 0 {
let value = su(r, 7)?;
*slot = i8::try_from(value).map_err(|_| DecodeError::InvalidInput)?;
}
}
}
Ok(LoopFilterParams {
level: [level0, level1],
level_u,
level_v,
sharpness,
delta_enabled,
delta_update,
ref_deltas,
mode_deltas,
})
}
impl FrameHeader {
#[allow(
clippy::too_many_lines,
reason = "one linear, spec-section-ordered read sequence (uncompressed_header() plus \
its called sub-syntax-structures); splitting the sub-structures into helper \
functions (parse_quantization_params/parse_loop_filter_params, above) already \
keeps each individually short — the remainder is uncompressed_header()'s own \
single top-level control flow, which has no independently reusable pieces"
)]
pub(super) fn parse(data: &[u8], seq: &SequenceHeader) -> Result<Self, DecodeError> {
let mut r = BitReader::new(data);
let map_err = |_| DecodeError::InvalidInput;
let show_existing_frame = r.read_bit().map_err(map_err)? != 0;
if show_existing_frame {
return Err(DecodeError::Unsupported);
}
let frame_type = r.read_bits(2).map_err(map_err)?;
if frame_type != 0 {
return Err(DecodeError::Unsupported); }
let show_frame = r.read_bit().map_err(map_err)? != 0;
if !show_frame {
return Err(DecodeError::Unsupported);
}
let disable_cdf_update = r.read_bit().map_err(map_err)? != 0;
let allow_screen_content_tools = if seq.seq_force_screen_content_tools == SELECT_VALUE {
r.read_bit().map_err(map_err)? != 0
} else {
seq.seq_force_screen_content_tools != 0
};
if allow_screen_content_tools && seq.seq_force_integer_mv == SELECT_VALUE {
let _force_integer_mv = r.read_bit().map_err(map_err)?;
}
let frame_size_override_flag = r.read_bit().map_err(map_err)? != 0;
let order_hint = r.read_bits(seq.order_hint_bits).map_err(map_err)?;
let order_hint = u8::try_from(order_hint).unwrap_or(0);
let (frame_width_minus1, frame_height_minus1) = if frame_size_override_flag {
(
r.read_bits(seq.frame_width_bits_minus_1 + 1)
.map_err(map_err)?,
r.read_bits(seq.frame_height_bits_minus_1 + 1)
.map_err(map_err)?,
)
} else {
(seq.max_frame_width_minus_1, seq.max_frame_height_minus_1)
};
let render_and_frame_size_different = r.read_bit().map_err(map_err)? != 0;
if render_and_frame_size_different {
let _render_width_minus_1 = r.read_bits(16).map_err(map_err)?;
let _render_height_minus_1 = r.read_bits(16).map_err(map_err)?;
}
let allow_intrabc = allow_screen_content_tools && r.read_bit().map_err(map_err)? != 0;
let disable_frame_end_update_cdf = if disable_cdf_update {
true
} else {
r.read_bit().map_err(map_err)? != 0
};
let frame_width_minus1_u16 =
u16::try_from(frame_width_minus1).map_err(|_| DecodeError::InvalidInput)?;
let frame_height_minus1_u16 =
u16::try_from(frame_height_minus1).map_err(|_| DecodeError::InvalidInput)?;
let mi_cols = 2 * ((frame_width_minus1 + 1 + 7) >> 3);
let mi_rows = 2 * ((frame_height_minus1 + 1 + 7) >> 3);
let tile_info = tile_info::parse(&mut r, seq.use_128x128_superblock, mi_cols, mi_rows)?;
let quantization = parse_quantization_params(&mut r, seq.separate_uv_delta_q)?;
let segmentation_enabled = r.read_bit().map_err(map_err)? != 0;
if segmentation_enabled {
return Err(DecodeError::Unsupported);
}
let delta_q_present = quantization.base_q_idx > 0 && r.read_bit().map_err(map_err)? != 0;
let delta_q_res = if delta_q_present {
u8::try_from(r.read_bits(2).map_err(map_err)?).unwrap_or(0)
} else {
0
};
let (delta_lf_present, delta_lf_res, delta_lf_multi) = if delta_q_present {
let present = !allow_intrabc && r.read_bit().map_err(map_err)? != 0;
if present {
(
true,
u8::try_from(r.read_bits(2).map_err(map_err)?).unwrap_or(0),
r.read_bit().map_err(map_err)? != 0,
)
} else {
(false, 0, false)
}
} else {
(false, 0, false)
};
let coded_lossless = is_coded_lossless(&quantization);
let loop_filter = parse_loop_filter_params(&mut r, coded_lossless, allow_intrabc)?;
let tx_mode = if coded_lossless {
0u32 } else if r.read_bit().map_err(map_err)? != 0 {
2 } else {
1 };
let reduced_tx_set = r.read_bit().map_err(map_err)? != 0;
Ok(Self {
frame_width_minus1: frame_width_minus1_u16,
frame_height_minus1: frame_height_minus1_u16,
order_hint,
disable_cdf_update,
disable_frame_end_update_cdf,
allow_screen_content_tools,
allow_intrabc,
tile_info,
quantization,
delta_q_present,
delta_q_res,
delta_lf_present,
delta_lf_res,
delta_lf_multi,
coded_lossless,
loop_filter,
tx_mode,
reduced_tx_set,
bits_consumed: r.bits_read(),
})
}
}
#[cfg(test)]
#[path = "frame_header_tests.rs"]
mod tests;