#![forbid(unsafe_code)]
#![allow(dead_code)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::unused_self)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::struct_field_names)]
#![allow(clippy::manual_div_ceil)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unnecessary_cast)]
#![allow(clippy::identity_op)]
#![allow(clippy::if_not_else)]
use super::cdef::CdefParams;
use super::loop_filter::LoopFilterParams;
use super::quantization::QuantizationParams;
use super::sequence::SequenceHeader;
use super::tile::TileInfo;
use crate::error::{CodecError, CodecResult};
use oximedia_io::BitReader;
pub const NUM_REF_FRAMES: usize = 8;
pub const REFS_PER_FRAME: usize = 7;
pub const MAX_SEGMENTS: usize = 8;
pub const SEG_LVL_MAX: usize = 8;
pub const PRIMARY_REF_NONE: u8 = 7;
pub const SUPERRES_DENOM_BITS: u8 = 3;
pub const SUPERRES_DENOM_MIN: u32 = 9;
pub const SUPERRES_NUM: u32 = 8;
pub const LAST_FRAME: usize = 1;
pub const LAST2_FRAME: usize = 2;
pub const LAST3_FRAME: usize = 3;
pub const GOLDEN_FRAME: usize = 4;
pub const BWDREF_FRAME: usize = 5;
pub const ALTREF2_FRAME: usize = 6;
pub const ALTREF_FRAME: usize = 7;
pub const INTERP_FILTER_EIGHTTAP: u8 = 0;
pub const INTERP_FILTER_EIGHTTAP_SMOOTH: u8 = 1;
pub const INTERP_FILTER_EIGHTTAP_SHARP: u8 = 2;
pub const INTERP_FILTER_BILINEAR: u8 = 3;
pub const INTERP_FILTER_SWITCHABLE: u8 = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum FrameType {
#[default]
KeyFrame = 0,
InterFrame = 1,
IntraOnlyFrame = 2,
SwitchFrame = 3,
}
impl FrameType {
#[must_use]
pub const fn is_intra(self) -> bool {
matches!(self, Self::KeyFrame | Self::IntraOnlyFrame)
}
#[must_use]
pub const fn is_key(self) -> bool {
matches!(self, Self::KeyFrame)
}
#[must_use]
pub const fn is_inter(self) -> bool {
matches!(self, Self::InterFrame | Self::SwitchFrame)
}
}
impl From<u8> for FrameType {
fn from(value: u8) -> Self {
match value {
0 => Self::KeyFrame,
1 => Self::InterFrame,
2 => Self::IntraOnlyFrame,
3 => Self::SwitchFrame,
_ => Self::KeyFrame, }
}
}
impl From<FrameType> for u8 {
fn from(ft: FrameType) -> Self {
ft as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum InterpolationFilter {
#[default]
Eighttap = 0,
EighttapSmooth = 1,
EighttapSharp = 2,
Bilinear = 3,
Switchable = 4,
}
impl From<u8> for InterpolationFilter {
fn from(value: u8) -> Self {
match value {
0 => Self::Eighttap,
1 => Self::EighttapSmooth,
2 => Self::EighttapSharp,
3 => Self::Bilinear,
_ => Self::Switchable,
}
}
}
impl From<InterpolationFilter> for u8 {
fn from(f: InterpolationFilter) -> Self {
f as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum MotionMode {
#[default]
Simple = 0,
ObstructedMotion = 1,
LocalWarp = 2,
}
impl From<u8> for MotionMode {
fn from(value: u8) -> Self {
match value {
0 => Self::Simple,
1 => Self::ObstructedMotion,
2 => Self::LocalWarp,
_ => Self::Simple,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ReferenceMode {
#[default]
SingleReference = 0,
CompoundReference = 1,
ReferenceModeSelect = 2,
}
impl From<u8> for ReferenceMode {
fn from(value: u8) -> Self {
match value {
0 => Self::SingleReference,
1 => Self::CompoundReference,
_ => Self::ReferenceModeSelect,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum TxMode {
Only4x4 = 0,
#[default]
Largest = 1,
Select = 2,
}
impl From<u8> for TxMode {
fn from(value: u8) -> Self {
match value {
0 => Self::Only4x4,
1 => Self::Largest,
_ => Self::Select,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum RestorationType {
#[default]
None = 0,
Wiener = 1,
SgrProj = 2,
Switchable = 3,
}
impl From<u8> for RestorationType {
fn from(value: u8) -> Self {
match value {
0 => Self::None,
1 => Self::Wiener,
2 => Self::SgrProj,
_ => Self::Switchable,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FrameSize {
pub frame_width: u32,
pub frame_height: u32,
pub upscaled_width: u32,
pub superres_denom: u32,
pub use_superres: bool,
pub mi_cols: u32,
pub mi_rows: u32,
}
impl FrameSize {
#[must_use]
pub const fn size_to_mi(size: u32) -> u32 {
(size + 3) >> 2
}
#[must_use]
pub const fn size_to_sb(size: u32, sb_size: u32) -> u32 {
(size + sb_size - 1) / sb_size
}
#[must_use]
pub fn sb_cols(&self, sb_size: u32) -> u32 {
Self::size_to_sb(self.upscaled_width, sb_size)
}
#[must_use]
pub fn sb_rows(&self, sb_size: u32) -> u32 {
Self::size_to_sb(self.frame_height, sb_size)
}
}
#[derive(Clone, Debug, Default)]
pub struct RenderSize {
pub render_width: u32,
pub render_height: u32,
pub render_and_frame_size_different: bool,
}
#[derive(Clone, Debug, Default)]
pub struct RefFrameInfo {
pub ref_frame_idx: [u8; REFS_PER_FRAME],
pub ref_order_hint: [u8; NUM_REF_FRAMES],
pub ref_frame_sign_bias: [bool; NUM_REF_FRAMES],
}
#[derive(Clone, Debug, Default)]
pub struct SegmentationParams {
pub enabled: bool,
pub update_map: bool,
pub temporal_update: bool,
pub update_data: bool,
pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS],
pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS],
pub last_active_seg_id: u8,
}
impl SegmentationParams {
pub const SEG_LVL_ALT_Q: usize = 0;
pub const SEG_LVL_ALT_LF_Y_V: usize = 1;
pub const SEG_LVL_ALT_LF_Y_H: usize = 2;
pub const SEG_LVL_ALT_LF_U: usize = 3;
pub const SEG_LVL_ALT_LF_V: usize = 4;
pub const SEG_LVL_REF_FRAME: usize = 5;
pub const SEG_LVL_SKIP: usize = 6;
pub const SEG_LVL_GLOBALMV: usize = 7;
pub const SEG_FEATURE_DATA_MAX: [i16; SEG_LVL_MAX] = [255, 63, 63, 63, 63, 7, 0, 0];
pub const SEG_FEATURE_DATA_SIGNED: [bool; SEG_LVL_MAX] =
[true, true, true, true, true, false, false, false];
pub const SEG_FEATURE_BITS: [u8; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0];
#[must_use]
pub fn get_feature(&self, segment_id: usize, feature: usize) -> i16 {
if segment_id < MAX_SEGMENTS
&& feature < SEG_LVL_MAX
&& self.feature_enabled[segment_id][feature]
{
self.feature_data[segment_id][feature]
} else {
0
}
}
#[must_use]
pub fn is_feature_enabled(&self, segment_id: usize, feature: usize) -> bool {
segment_id < MAX_SEGMENTS
&& feature < SEG_LVL_MAX
&& self.feature_enabled[segment_id][feature]
}
}
#[derive(Clone, Debug, Default)]
pub struct LoopRestorationParams {
pub frame_restoration_type: [RestorationType; 3],
pub loop_restoration_size: [u8; 3],
pub uses_lr: bool,
}
#[derive(Clone, Debug, Default)]
pub struct GlobalMotionParams {
pub gm_type: u8,
pub gm_params: [i32; 6],
}
#[derive(Clone, Debug)]
pub struct GlobalMotion {
pub params: [GlobalMotionParams; NUM_REF_FRAMES],
}
impl Default for GlobalMotion {
fn default() -> Self {
Self {
params: std::array::from_fn(|_| GlobalMotionParams::default()),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FilmGrainParams {
pub apply_grain: bool,
pub grain_seed: u16,
pub update_grain: bool,
pub num_y_points: u8,
pub point_y_value: [u8; 14],
pub point_y_scaling: [u8; 14],
pub chroma_scaling_from_luma: bool,
pub num_cb_points: u8,
pub point_cb_value: [u8; 10],
pub point_cb_scaling: [u8; 10],
pub num_cr_points: u8,
pub point_cr_value: [u8; 10],
pub point_cr_scaling: [u8; 10],
pub grain_scaling_minus_8: u8,
pub ar_coeff_lag: u8,
pub ar_coeffs_y_plus_128: [u8; 24],
pub ar_coeffs_cb_plus_128: [u8; 25],
pub ar_coeffs_cr_plus_128: [u8; 25],
pub ar_coeff_shift_minus_6: u8,
pub grain_scale_shift: u8,
pub cb_mult: u8,
pub cb_luma_mult: u8,
pub cb_offset: u16,
pub cr_mult: u8,
pub cr_luma_mult: u8,
pub cr_offset: u16,
pub overlap_flag: bool,
pub clip_to_restricted_range: bool,
}
#[derive(Clone, Debug, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct FrameHeader {
pub frame_type: FrameType,
pub show_frame: bool,
pub showable_frame: bool,
pub show_existing_frame: bool,
pub frame_to_show_map_idx: u8,
pub error_resilient_mode: bool,
pub current_frame_id: u32,
pub order_hint: u8,
pub primary_ref_frame: u8,
pub refresh_frame_flags: u8,
pub frame_size: FrameSize,
pub render_size: RenderSize,
pub allow_high_precision_mv: bool,
pub interpolation_filter: InterpolationFilter,
pub is_filter_switchable: bool,
pub allow_intrabc: bool,
pub ref_frame_info: RefFrameInfo,
pub allow_screen_content_tools: bool,
pub force_integer_mv: bool,
pub is_motion_mode_switchable: bool,
pub use_ref_frame_mvs: bool,
pub reference_mode: ReferenceMode,
pub skip_mode_frame: [u8; 2],
pub skip_mode_allowed: bool,
pub skip_mode_present: bool,
pub compound_reference_allowed: bool,
pub tx_mode: TxMode,
pub reduced_tx_set: bool,
pub allow_warped_motion: bool,
pub quantization: QuantizationParams,
pub segmentation: SegmentationParams,
pub loop_filter: LoopFilterParams,
pub cdef: CdefParams,
pub loop_restoration: LoopRestorationParams,
pub tile_info: TileInfo,
pub global_motion: GlobalMotion,
pub film_grain: FilmGrainParams,
pub frame_is_intra: bool,
pub lossless_array: [bool; MAX_SEGMENTS],
pub coded_lossless: bool,
pub all_lossless: bool,
}
impl FrameHeader {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
pub fn parse(data: &[u8], seq: &SequenceHeader) -> CodecResult<Self> {
let mut reader = BitReader::new(data);
let mut header = Self::new();
header.parse_uncompressed_header(&mut reader, seq)?;
Ok(header)
}
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
fn parse_uncompressed_header(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
let frame_id_length = if seq.reduced_still_picture_header {
0
} else {
15
};
if seq.reduced_still_picture_header {
self.show_existing_frame = false;
self.frame_type = FrameType::KeyFrame;
self.show_frame = true;
self.showable_frame = false;
} else {
self.show_existing_frame = reader.read_bit().map_err(CodecError::Core)? != 0;
if self.show_existing_frame {
self.frame_to_show_map_idx = reader.read_bits(3).map_err(CodecError::Core)? as u8;
if frame_id_length > 0 {
self.current_frame_id = reader
.read_bits(frame_id_length)
.map_err(CodecError::Core)?
as u32;
}
return Ok(());
}
self.frame_type = FrameType::from(reader.read_bits(2).map_err(CodecError::Core)? as u8);
self.show_frame = reader.read_bit().map_err(CodecError::Core)? != 0;
if self.show_frame && !seq.reduced_still_picture_header {
}
self.showable_frame = if self.show_frame {
!self.frame_type.is_key()
} else {
reader.read_bit().map_err(CodecError::Core)? != 0
};
self.error_resilient_mode =
if self.frame_type == FrameType::SwitchFrame || self.frame_type.is_key() {
true
} else {
reader.read_bit().map_err(CodecError::Core)? != 0
};
}
self.frame_is_intra = self.frame_type.is_intra();
if !seq.reduced_still_picture_header {
let _disable_cdf_update = reader.read_bit().map_err(CodecError::Core)?;
}
self.allow_screen_content_tools = if seq.reduced_still_picture_header {
false
} else {
reader.read_bit().map_err(CodecError::Core)? != 0
};
self.force_integer_mv = if self.allow_screen_content_tools {
if !seq.reduced_still_picture_header {
reader.read_bit().map_err(CodecError::Core)? != 0
} else {
false
}
} else {
false
};
if frame_id_length > 0 {
self.current_frame_id = reader
.read_bits(frame_id_length)
.map_err(CodecError::Core)? as u32;
}
self.parse_frame_size(reader, seq)?;
self.parse_render_size(reader)?;
if seq.enable_superres && !self.frame_size.use_superres {
self.frame_size.use_superres = reader.read_bit().map_err(CodecError::Core)? != 0;
if self.frame_size.use_superres {
let coded_denom = reader
.read_bits(SUPERRES_DENOM_BITS)
.map_err(CodecError::Core)? as u32
+ SUPERRES_DENOM_MIN;
self.frame_size.superres_denom = coded_denom;
self.frame_size.upscaled_width = self.frame_size.frame_width;
self.frame_size.frame_width =
(self.frame_size.upscaled_width * SUPERRES_NUM + coded_denom / 2) / coded_denom;
}
}
if !self.frame_is_intra {
self.parse_inter_frame_params(reader, seq)?;
}
self.quantization = QuantizationParams::parse(reader, seq)?;
self.parse_segmentation(reader)?;
self.compute_lossless(seq);
if !self.coded_lossless {
self.loop_filter = LoopFilterParams::parse(reader, seq, self.frame_is_intra)?;
}
if seq.enable_cdef && !self.coded_lossless && !self.allow_intrabc {
self.cdef = CdefParams::parse(reader, seq)?;
}
if seq.enable_restoration && !self.all_lossless && !self.allow_intrabc {
self.parse_loop_restoration(reader, seq)?;
}
self.parse_tx_mode(reader)?;
if !self.frame_is_intra {
self.reference_mode = if reader.read_bit().map_err(CodecError::Core)? != 0 {
ReferenceMode::ReferenceModeSelect
} else {
ReferenceMode::SingleReference
};
}
self.parse_skip_mode(reader, seq)?;
if !self.frame_is_intra && !self.error_resilient_mode {
self.allow_warped_motion = reader.read_bit().map_err(CodecError::Core)? != 0;
}
self.reduced_tx_set = reader.read_bit().map_err(CodecError::Core)? != 0;
if !self.frame_is_intra {
self.parse_global_motion(reader)?;
}
if seq.film_grain_params_present && (self.show_frame || self.showable_frame) {
self.parse_film_grain(reader, seq)?;
}
self.tile_info = TileInfo::parse(reader, seq, &self.frame_size)?;
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_frame_size(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
if self.frame_type == FrameType::SwitchFrame {
self.frame_size.frame_width = seq.max_frame_width();
self.frame_size.frame_height = seq.max_frame_height();
} else {
let frame_size_override = if seq.reduced_still_picture_header {
false
} else {
reader.read_bit().map_err(CodecError::Core)? != 0
};
if frame_size_override {
let frame_width_bits = 16; let frame_height_bits = 16;
self.frame_size.frame_width = reader
.read_bits(frame_width_bits)
.map_err(CodecError::Core)?
as u32
+ 1;
self.frame_size.frame_height = reader
.read_bits(frame_height_bits)
.map_err(CodecError::Core)?
as u32
+ 1;
} else {
self.frame_size.frame_width = seq.max_frame_width();
self.frame_size.frame_height = seq.max_frame_height();
}
}
self.frame_size.upscaled_width = self.frame_size.frame_width;
self.frame_size.superres_denom = SUPERRES_NUM;
self.frame_size.mi_cols = FrameSize::size_to_mi(self.frame_size.upscaled_width);
self.frame_size.mi_rows = FrameSize::size_to_mi(self.frame_size.frame_height);
Ok(())
}
fn parse_render_size(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
self.render_size.render_and_frame_size_different =
reader.read_bit().map_err(CodecError::Core)? != 0;
if self.render_size.render_and_frame_size_different {
let render_width_minus_1 = reader.read_bits(16).map_err(CodecError::Core)? as u32;
let render_height_minus_1 = reader.read_bits(16).map_err(CodecError::Core)? as u32;
self.render_size.render_width = render_width_minus_1 + 1;
self.render_size.render_height = render_height_minus_1 + 1;
} else {
self.render_size.render_width = self.frame_size.upscaled_width;
self.render_size.render_height = self.frame_size.frame_height;
}
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_inter_frame_params(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
for i in 0..REFS_PER_FRAME {
self.ref_frame_info.ref_frame_idx[i] =
reader.read_bits(3).map_err(CodecError::Core)? as u8;
}
if !self.error_resilient_mode && self.frame_type != FrameType::SwitchFrame {
}
self.allow_high_precision_mv = if self.force_integer_mv {
false
} else {
reader.read_bit().map_err(CodecError::Core)? != 0
};
self.is_filter_switchable = reader.read_bit().map_err(CodecError::Core)? != 0;
self.interpolation_filter = if self.is_filter_switchable {
InterpolationFilter::Switchable
} else {
InterpolationFilter::from(reader.read_bits(2).map_err(CodecError::Core)? as u8)
};
self.is_motion_mode_switchable = reader.read_bit().map_err(CodecError::Core)? != 0;
if !self.error_resilient_mode && seq.enable_order_hint {
self.use_ref_frame_mvs = reader.read_bit().map_err(CodecError::Core)? != 0;
}
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_segmentation(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
self.segmentation.enabled = reader.read_bit().map_err(CodecError::Core)? != 0;
if !self.segmentation.enabled {
return Ok(());
}
if self.primary_ref_frame == PRIMARY_REF_NONE {
self.segmentation.update_map = true;
self.segmentation.temporal_update = false;
self.segmentation.update_data = true;
} else {
self.segmentation.update_map = reader.read_bit().map_err(CodecError::Core)? != 0;
if self.segmentation.update_map {
self.segmentation.temporal_update =
reader.read_bit().map_err(CodecError::Core)? != 0;
}
self.segmentation.update_data = reader.read_bit().map_err(CodecError::Core)? != 0;
}
if self.segmentation.update_data {
for i in 0..MAX_SEGMENTS {
for j in 0..SEG_LVL_MAX {
self.segmentation.feature_enabled[i][j] =
reader.read_bit().map_err(CodecError::Core)? != 0;
if self.segmentation.feature_enabled[i][j] {
let bits = SegmentationParams::SEG_FEATURE_BITS[j];
let max = SegmentationParams::SEG_FEATURE_DATA_MAX[j];
if SegmentationParams::SEG_FEATURE_DATA_SIGNED[j] {
let value =
reader.read_bits(bits + 1).map_err(CodecError::Core)? as i16;
let sign = if value & (1 << bits) != 0 { -1 } else { 1 };
let magnitude = value & ((1 << bits) - 1);
self.segmentation.feature_data[i][j] =
(sign * magnitude).clamp(-max, max);
} else {
self.segmentation.feature_data[i][j] =
(reader.read_bits(bits).map_err(CodecError::Core)? as i16).min(max);
}
}
}
}
}
self.segmentation.last_active_seg_id = 0;
for i in 0..MAX_SEGMENTS {
for j in 0..SEG_LVL_MAX {
if self.segmentation.feature_enabled[i][j] {
self.segmentation.last_active_seg_id = i as u8;
}
}
}
Ok(())
}
fn compute_lossless(&mut self, seq: &SequenceHeader) {
for seg_id in 0..MAX_SEGMENTS {
let qindex = self.get_qindex(seg_id);
let lossless = qindex == 0
&& self.quantization.delta_q_y_dc == 0
&& self.quantization.delta_q_u_ac == 0
&& self.quantization.delta_q_u_dc == 0
&& self.quantization.delta_q_v_ac == 0
&& self.quantization.delta_q_v_dc == 0;
self.lossless_array[seg_id] = lossless;
}
self.coded_lossless = self.lossless_array.iter().all(|&l| l);
self.all_lossless =
self.coded_lossless && self.frame_size.frame_width == self.frame_size.upscaled_width;
if self.coded_lossless {
self.loop_filter.level[0] = 0;
self.loop_filter.level[1] = 0;
}
if seq.color_config.mono_chrome {
self.quantization.delta_q_u_dc = 0;
self.quantization.delta_q_u_ac = 0;
self.quantization.delta_q_v_dc = 0;
self.quantization.delta_q_v_ac = 0;
}
}
#[must_use]
pub fn get_qindex(&self, seg_id: usize) -> u8 {
let base_q = self.quantization.base_q_idx;
if self.segmentation.enabled
&& self
.segmentation
.is_feature_enabled(seg_id, SegmentationParams::SEG_LVL_ALT_Q)
{
let delta = self
.segmentation
.get_feature(seg_id, SegmentationParams::SEG_LVL_ALT_Q);
let q = i32::from(base_q) + i32::from(delta);
q.clamp(0, 255) as u8
} else {
base_q
}
}
#[allow(clippy::cast_possible_truncation)]
fn parse_loop_restoration(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
let num_planes = if seq.color_config.mono_chrome { 1 } else { 3 };
let mut uses_lr = false;
let mut uses_chroma_lr = false;
for plane in 0..num_planes {
let lr_type = reader.read_bits(2).map_err(CodecError::Core)? as u8;
self.loop_restoration.frame_restoration_type[plane] = RestorationType::from(lr_type);
if lr_type != 0 {
uses_lr = true;
if plane > 0 {
uses_chroma_lr = true;
}
}
}
self.loop_restoration.uses_lr = uses_lr;
if uses_lr {
let lr_unit_shift = if seq.enable_superres {
reader.read_bit().map_err(CodecError::Core)? as u8
} else {
1
};
let lr_unit_extra_shift = if lr_unit_shift != 0 && !seq.enable_superres {
reader.read_bit().map_err(CodecError::Core)? as u8
} else {
0
};
let sb_size = 64; let lr_size_base = 6 + lr_unit_shift + lr_unit_extra_shift;
self.loop_restoration.loop_restoration_size[0] = lr_size_base.min(sb_size);
if uses_chroma_lr && !seq.color_config.is_420() {
let uv_shift = reader.read_bit().map_err(CodecError::Core)? as u8;
self.loop_restoration.loop_restoration_size[1] = lr_size_base - uv_shift;
self.loop_restoration.loop_restoration_size[2] = lr_size_base - uv_shift;
} else {
self.loop_restoration.loop_restoration_size[1] = lr_size_base;
self.loop_restoration.loop_restoration_size[2] = lr_size_base;
}
}
Ok(())
}
fn parse_tx_mode(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
if self.coded_lossless {
self.tx_mode = TxMode::Only4x4;
} else {
let tx_mode_select = reader.read_bit().map_err(CodecError::Core)? != 0;
self.tx_mode = if tx_mode_select {
TxMode::Select
} else {
TxMode::Largest
};
}
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_skip_mode(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
if self.frame_is_intra
|| !self.reference_mode.eq(&ReferenceMode::ReferenceModeSelect)
|| !seq.enable_order_hint
{
self.skip_mode_allowed = false;
} else {
self.skip_mode_allowed = true;
}
if self.skip_mode_allowed {
self.skip_mode_present = reader.read_bit().map_err(CodecError::Core)? != 0;
} else {
self.skip_mode_present = false;
}
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_global_motion(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
for ref_frame in LAST_FRAME..=ALTREF_FRAME {
let is_global = reader.read_bit().map_err(CodecError::Core)? != 0;
if is_global {
let is_rot_zoom = reader.read_bit().map_err(CodecError::Core)? != 0;
if is_rot_zoom {
self.global_motion.params[ref_frame].gm_type = 2; } else {
let is_translation = reader.read_bit().map_err(CodecError::Core)? != 0;
self.global_motion.params[ref_frame].gm_type = if is_translation {
1 } else {
3 };
}
} else {
self.global_motion.params[ref_frame].gm_type = 0; }
}
Ok(())
}
#[allow(clippy::cast_possible_truncation)]
fn parse_film_grain(
&mut self,
reader: &mut BitReader<'_>,
seq: &SequenceHeader,
) -> CodecResult<()> {
self.film_grain.apply_grain = reader.read_bit().map_err(CodecError::Core)? != 0;
if !self.film_grain.apply_grain {
return Ok(());
}
self.film_grain.grain_seed = reader.read_bits(16).map_err(CodecError::Core)? as u16;
if self.frame_type == FrameType::InterFrame {
self.film_grain.update_grain = reader.read_bit().map_err(CodecError::Core)? != 0;
} else {
self.film_grain.update_grain = true;
}
if !self.film_grain.update_grain {
let _film_grain_params_ref_idx = reader.read_bits(3).map_err(CodecError::Core)?;
return Ok(());
}
self.film_grain.num_y_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
for i in 0..self.film_grain.num_y_points as usize {
self.film_grain.point_y_value[i] = reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.point_y_scaling[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
}
self.film_grain.chroma_scaling_from_luma = if !seq.color_config.mono_chrome {
reader.read_bit().map_err(CodecError::Core)? != 0
} else {
false
};
if seq.color_config.mono_chrome
|| self.film_grain.chroma_scaling_from_luma
|| (seq.color_config.is_420() && self.film_grain.num_y_points == 0)
{
self.film_grain.num_cb_points = 0;
self.film_grain.num_cr_points = 0;
} else {
self.film_grain.num_cb_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
for i in 0..self.film_grain.num_cb_points as usize {
self.film_grain.point_cb_value[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.point_cb_scaling[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
}
self.film_grain.num_cr_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
for i in 0..self.film_grain.num_cr_points as usize {
self.film_grain.point_cr_value[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.point_cr_scaling[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
}
}
self.film_grain.grain_scaling_minus_8 =
reader.read_bits(2).map_err(CodecError::Core)? as u8;
self.film_grain.ar_coeff_lag = reader.read_bits(2).map_err(CodecError::Core)? as u8;
let num_pos_luma = 2 * self.film_grain.ar_coeff_lag * (self.film_grain.ar_coeff_lag + 1);
for i in 0..num_pos_luma as usize {
if self.film_grain.num_y_points > 0 && i < 24 {
self.film_grain.ar_coeffs_y_plus_128[i] =
reader.read_bits(8).map_err(CodecError::Core)? as u8;
}
}
self.film_grain.ar_coeff_shift_minus_6 =
reader.read_bits(2).map_err(CodecError::Core)? as u8;
self.film_grain.grain_scale_shift = reader.read_bits(2).map_err(CodecError::Core)? as u8;
if self.film_grain.num_cb_points > 0 {
self.film_grain.cb_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.cb_luma_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.cb_offset = reader.read_bits(9).map_err(CodecError::Core)? as u16;
}
if self.film_grain.num_cr_points > 0 {
self.film_grain.cr_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.cr_luma_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
self.film_grain.cr_offset = reader.read_bits(9).map_err(CodecError::Core)? as u16;
}
self.film_grain.overlap_flag = reader.read_bit().map_err(CodecError::Core)? != 0;
self.film_grain.clip_to_restricted_range =
reader.read_bit().map_err(CodecError::Core)? != 0;
Ok(())
}
#[must_use]
pub const fn is_key_frame(&self) -> bool {
matches!(self.frame_type, FrameType::KeyFrame)
}
#[must_use]
pub const fn is_inter_frame(&self) -> bool {
matches!(
self.frame_type,
FrameType::InterFrame | FrameType::SwitchFrame
)
}
#[must_use]
pub const fn display_order(&self) -> u8 {
self.order_hint
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_type_conversions() {
assert_eq!(FrameType::from(0), FrameType::KeyFrame);
assert_eq!(FrameType::from(1), FrameType::InterFrame);
assert_eq!(FrameType::from(2), FrameType::IntraOnlyFrame);
assert_eq!(FrameType::from(3), FrameType::SwitchFrame);
assert_eq!(FrameType::from(4), FrameType::KeyFrame);
assert_eq!(u8::from(FrameType::KeyFrame), 0);
assert_eq!(u8::from(FrameType::InterFrame), 1);
}
#[test]
fn test_frame_type_properties() {
assert!(FrameType::KeyFrame.is_intra());
assert!(FrameType::KeyFrame.is_key());
assert!(!FrameType::KeyFrame.is_inter());
assert!(!FrameType::InterFrame.is_intra());
assert!(!FrameType::InterFrame.is_key());
assert!(FrameType::InterFrame.is_inter());
assert!(FrameType::IntraOnlyFrame.is_intra());
assert!(!FrameType::IntraOnlyFrame.is_key());
assert!(!FrameType::IntraOnlyFrame.is_inter());
assert!(!FrameType::SwitchFrame.is_intra());
assert!(!FrameType::SwitchFrame.is_key());
assert!(FrameType::SwitchFrame.is_inter());
}
#[test]
fn test_interpolation_filter_conversions() {
assert_eq!(InterpolationFilter::from(0), InterpolationFilter::Eighttap);
assert_eq!(
InterpolationFilter::from(1),
InterpolationFilter::EighttapSmooth
);
assert_eq!(
InterpolationFilter::from(2),
InterpolationFilter::EighttapSharp
);
assert_eq!(InterpolationFilter::from(3), InterpolationFilter::Bilinear);
assert_eq!(
InterpolationFilter::from(4),
InterpolationFilter::Switchable
);
}
#[test]
fn test_frame_size_calculations() {
assert_eq!(FrameSize::size_to_mi(1920), 480);
assert_eq!(FrameSize::size_to_mi(1080), 270);
assert_eq!(FrameSize::size_to_mi(1), 1);
assert_eq!(FrameSize::size_to_mi(4), 1);
assert_eq!(FrameSize::size_to_mi(5), 2);
assert_eq!(FrameSize::size_to_sb(1920, 64), 30);
assert_eq!(FrameSize::size_to_sb(1920, 128), 15);
assert_eq!(FrameSize::size_to_sb(1080, 64), 17);
}
#[test]
fn test_frame_size_sb_calculations() {
let frame_size = FrameSize {
frame_width: 1920,
frame_height: 1080,
upscaled_width: 1920,
superres_denom: 8,
use_superres: false,
mi_cols: 480,
mi_rows: 270,
};
assert_eq!(frame_size.sb_cols(64), 30);
assert_eq!(frame_size.sb_rows(64), 17);
assert_eq!(frame_size.sb_cols(128), 15);
assert_eq!(frame_size.sb_rows(128), 9);
}
#[test]
fn test_segmentation_features() {
let mut seg = SegmentationParams::default();
seg.enabled = true;
seg.feature_enabled[0][SegmentationParams::SEG_LVL_ALT_Q] = true;
seg.feature_data[0][SegmentationParams::SEG_LVL_ALT_Q] = 10;
assert!(seg.is_feature_enabled(0, SegmentationParams::SEG_LVL_ALT_Q));
assert_eq!(seg.get_feature(0, SegmentationParams::SEG_LVL_ALT_Q), 10);
assert_eq!(seg.get_feature(1, SegmentationParams::SEG_LVL_ALT_Q), 0);
assert!(!seg.is_feature_enabled(0, SegmentationParams::SEG_LVL_SKIP));
}
#[test]
fn test_motion_mode_conversion() {
assert_eq!(MotionMode::from(0), MotionMode::Simple);
assert_eq!(MotionMode::from(1), MotionMode::ObstructedMotion);
assert_eq!(MotionMode::from(2), MotionMode::LocalWarp);
assert_eq!(MotionMode::from(99), MotionMode::Simple);
}
#[test]
fn test_reference_mode_conversion() {
assert_eq!(ReferenceMode::from(0), ReferenceMode::SingleReference);
assert_eq!(ReferenceMode::from(1), ReferenceMode::CompoundReference);
assert_eq!(ReferenceMode::from(2), ReferenceMode::ReferenceModeSelect);
}
#[test]
fn test_tx_mode_conversion() {
assert_eq!(TxMode::from(0), TxMode::Only4x4);
assert_eq!(TxMode::from(1), TxMode::Largest);
assert_eq!(TxMode::from(2), TxMode::Select);
}
#[test]
fn test_restoration_type_conversion() {
assert_eq!(RestorationType::from(0), RestorationType::None);
assert_eq!(RestorationType::from(1), RestorationType::Wiener);
assert_eq!(RestorationType::from(2), RestorationType::SgrProj);
assert_eq!(RestorationType::from(3), RestorationType::Switchable);
}
#[test]
fn test_frame_header_default() {
let header = FrameHeader::new();
assert_eq!(header.frame_type, FrameType::KeyFrame);
assert!(!header.show_frame);
assert!(!header.error_resilient_mode);
assert!(!header.frame_is_intra);
}
#[test]
fn test_frame_header_queries() {
let mut header = FrameHeader::new();
header.frame_type = FrameType::KeyFrame;
assert!(header.is_key_frame());
assert!(!header.is_inter_frame());
header.frame_type = FrameType::InterFrame;
assert!(!header.is_key_frame());
assert!(header.is_inter_frame());
header.frame_type = FrameType::SwitchFrame;
assert!(!header.is_key_frame());
assert!(header.is_inter_frame());
header.order_hint = 42;
assert_eq!(header.display_order(), 42);
}
#[test]
fn test_get_qindex() {
let mut header = FrameHeader::new();
header.quantization.base_q_idx = 100;
assert_eq!(header.get_qindex(0), 100);
header.segmentation.enabled = true;
header.segmentation.feature_enabled[0][SegmentationParams::SEG_LVL_ALT_Q] = true;
header.segmentation.feature_data[0][SegmentationParams::SEG_LVL_ALT_Q] = -20;
assert_eq!(header.get_qindex(0), 80);
assert_eq!(header.get_qindex(1), 100);
}
#[test]
fn test_global_motion_default() {
let gm = GlobalMotion::default();
for i in 0..NUM_REF_FRAMES {
assert_eq!(gm.params[i].gm_type, 0);
}
}
#[test]
fn test_film_grain_defaults() {
let fg = FilmGrainParams::default();
assert!(!fg.apply_grain);
assert_eq!(fg.grain_seed, 0);
assert_eq!(fg.num_y_points, 0);
}
#[test]
fn test_render_size_defaults() {
let rs = RenderSize::default();
assert_eq!(rs.render_width, 0);
assert_eq!(rs.render_height, 0);
assert!(!rs.render_and_frame_size_different);
}
#[test]
fn test_loop_restoration_params_defaults() {
let lr = LoopRestorationParams::default();
assert!(!lr.uses_lr);
assert_eq!(lr.frame_restoration_type[0], RestorationType::None);
}
#[test]
fn test_ref_frame_info_defaults() {
let rfi = RefFrameInfo::default();
for i in 0..REFS_PER_FRAME {
assert_eq!(rfi.ref_frame_idx[i], 0);
}
for i in 0..NUM_REF_FRAMES {
assert_eq!(rfi.ref_order_hint[i], 0);
assert!(!rfi.ref_frame_sign_bias[i]);
}
}
}