use crate::av2::entropy::RangeEncoder;
use crate::av2::quant::qstep;
use crate::util::FastRound;
pub(crate) const AQ_RES_LOG2: u8 = 2;
const AQ_MAX_SIGNALED: i32 = 6;
pub(crate) fn sb_activity(
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> f32 {
let h = height.saturating_sub(sb_y).min(64);
let w = width.saturating_sub(sb_x).min(64);
if h == 0 || w == 0 {
return 0.0;
}
let mut sum = 0f64;
let mut sum2 = 0f64;
for r in 0..h {
let base = (sb_y + r) * pw + sb_x;
let yp = &yp[base..base + w];
for &c in yp.iter() {
let v = c as f64;
sum += v;
sum2 += v * v;
}
}
let n = (h * w) as f64;
let mean = sum / n;
let var = (sum2 / n - mean * mean).max(0.0);
(1.0 + var).ln() as f32
}
fn sb_subblock_variances(
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
out: &mut [f32; 64],
) -> usize {
let mut filled = 0usize;
let mut acc = 0f64;
for by in 0..8 {
for bx in 0..8 {
let y0 = sb_y + by * 8;
let x0 = sb_x + bx * 8;
let h = height.saturating_sub(y0).min(8);
let w = width.saturating_sub(x0).min(8);
let idx = by * 8 + bx;
if h == 0 || w == 0 {
out[idx] = f32::NAN; continue;
}
let mut sum = 0f64;
let mut sum2 = 0f64;
for r in 0..h {
let base = (y0 + r) * pw + x0;
for &v in &yp[base..base + w] {
let v = v as f64;
sum += v;
sum2 += v * v;
}
}
let n = (h * w) as f64;
let mean = sum / n;
let var = (sum2 / n - mean * mean).max(0.0) as f32;
out[idx] = var;
acc += var as f64;
filled += 1;
}
}
if filled == 0 {
out.iter_mut().for_each(|v| *v = 0.0);
return 0;
}
let mean = (acc / filled as f64) as f32;
for v in out.iter_mut() {
if v.is_nan() {
*v = mean;
}
}
filled
}
fn sb_octile_variance(subvars: &mut [f32; 64], octile: u8) -> f32 {
subvars.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let o = octile.clamp(1, 8) as usize;
let idx = (o * 8 - 1).min(63);
subvars[idx]
}
fn variance_boost_delta(picked_var: f32, ref_log: f32, strength: f32, boost_only: bool) -> i32 {
let v_log = (1.0 + picked_var).ln();
const LOW_LOG: f32 = 5.549_076; const MAX_BOOST: f32 = 18.0; const MAX_CUT: f32 = 10.0; const BOOST_SLOPE: f32 = 5.0;
const CUT_SLOPE: f32 = 3.0;
if v_log < LOW_LOG {
let d = ((LOW_LOG - v_log) * BOOST_SLOPE * strength).min(MAX_BOOST);
-(d.fast_round() as i32)
} else if boost_only {
0
} else {
let over = (v_log - ref_log.max(LOW_LOG)).max(0.0);
let d = (over * CUT_SLOPE * strength).min(MAX_CUT);
d.fast_round() as i32
}
}
pub(crate) fn tile_ref_activity(
yp: &[f32],
pw: usize,
sb_rows: usize,
sb_cols: usize,
width: usize,
height: usize,
) -> f32 {
let mut sum = 0f32;
let mut cnt = 0f32;
for row in 0..sb_rows {
for col in 0..sb_cols {
sum += sb_activity(yp, pw, row * 64, col * 64, width, height);
cnt += 1.0;
}
}
if cnt > 0.0 { sum / cnt } else { 5.0 }
}
pub(crate) fn scale_resid(v: &[f32], s: f32) -> Vec<f32> {
if s == 1.0 {
v.to_vec()
} else {
v.iter().map(|&x| x * s).collect()
}
}
pub(crate) struct AqState {
present: bool,
base_q: i32,
qstep_base: i32,
ref_act: f32,
last_qidx: i32,
vb_octile: u8,
vb_strength: f32,
vb_boost_only: bool,
}
impl AqState {
pub(crate) fn new(present: bool, base_q: i32, qstep_base: i32, ref_act: f32) -> Self {
AqState {
present,
base_q,
qstep_base,
ref_act,
last_qidx: base_q,
vb_octile: 6,
vb_strength: 1.0,
vb_boost_only: false,
}
}
pub(crate) fn with_variance_boost(
mut self,
octile: u8,
strength: f32,
boost_only: bool,
) -> Self {
self.vb_octile = octile.clamp(1, 8);
let taper = ((self.base_q as f32 - 30.0) / 40.0).clamp(0.0, 1.0);
self.vb_strength = strength.max(0.0) * taper;
self.vb_boost_only = boost_only;
self
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn per_sb_probe(
&self,
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> (i32, f32) {
if !self.present {
return (self.qstep_base, 1.0);
}
let mut subvars = [0f32; 64];
let filled = sb_subblock_variances(yp, pw, sb_y, sb_x, width, height, &mut subvars);
let target = if filled == 0 {
self.base_q
} else {
let picked = sb_octile_variance(&mut subvars, self.vb_octile);
let delta =
variance_boost_delta(picked, self.ref_act, self.vb_strength, self.vb_boost_only);
(self.base_q + delta).clamp(1, 255)
};
let step = 1i32 << AQ_RES_LOG2;
let sig = (((target - self.last_qidx) as f32) / step as f32)
.fast_round()
.clamp(-(AQ_MAX_SIGNALED as f32), AQ_MAX_SIGNALED as f32) as i32;
let newq = (self.last_qidx + sig * step).clamp(1, 255);
let qs = qstep(newq as u32) as i32;
(qs, self.qstep_base as f32 / qs as f32)
}
pub(crate) fn current(&self) -> (i32, f32) {
if !self.present {
return (self.qstep_base, 1.0);
}
let qs = qstep(self.last_qidx as u32) as i32;
(qs, self.qstep_base as f32 / qs as f32)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn per_sb(
&mut self,
enc: &mut RangeEncoder,
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> (i32, f32) {
if !self.present {
enc.delta_q_signaled = 0;
return (self.qstep_base, 1.0);
}
let mut subvars = [0f32; 64];
let filled = sb_subblock_variances(yp, pw, sb_y, sb_x, width, height, &mut subvars);
let target = if filled == 0 {
self.base_q
} else {
let picked = sb_octile_variance(&mut subvars, self.vb_octile);
let delta =
variance_boost_delta(picked, self.ref_act, self.vb_strength, self.vb_boost_only);
(self.base_q + delta).clamp(1, 255)
};
let step = 1i32 << AQ_RES_LOG2;
let sig = (((target - self.last_qidx) as f32) / step as f32)
.fast_round()
.clamp(-(AQ_MAX_SIGNALED as f32), AQ_MAX_SIGNALED as f32) as i32;
let newq = (self.last_qidx + sig * step).clamp(1, 255);
self.last_qidx = newq;
enc.delta_q_signaled = sig;
let qs = qstep(newq as u32) as i32;
(qs, self.qstep_base as f32 / qs as f32)
}
}