use crate::av2::entropy::RangeEncoder;
use crate::av2::helpers::sum_sumsq_f32;
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 = 0f32;
let mut sum2 = 0f32;
for r in 0..h {
let base = (sb_y + r) * pw + sb_x;
let (row_sum, row_sum2) = sum_sumsq_f32(&yp[base..base + w]);
sum += row_sum;
sum2 += row_sum2;
}
let n = (h * w) as f32;
let mean = sum / n;
let var = (sum2 / n - mean * mean).max(0.0);
(1.0 + var).ln()
}
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 = 0f32;
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 = 0f32;
let mut sum2 = 0f32;
for r in 0..h {
let base = (y0 + r) * pw + x0;
let (row_sum, row_sum2) = sum_sumsq_f32(&yp[base..base + w]);
sum += row_sum;
sum2 += row_sum2;
}
let n = (h * w) as f32;
let mean = sum / n;
let var = (sum2 / n - mean * mean).max(0.0);
out[idx] = var;
acc += var;
filled += 1;
}
}
if filled == 0 {
out.iter_mut().for_each(|v| *v = 0.0);
return 0;
}
let mean = acc / filled 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
}
}
fn dark_structure_stats(
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> (f32, 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, 0.0);
}
let mut buf = [[0f32; 64]; 64];
let mut sum = 0f32;
for (r, row) in buf.iter_mut().enumerate().take(h) {
let base = (sb_y + r) * pw + sb_x;
for c in 0..w {
let v = yp[base + c];
row[c] = v;
sum += v;
}
}
let mean = sum / (h * w) as f32;
if h < 3 || w < 3 {
return (mean, 0.0);
}
let mut lap_full = 0f32;
let mut nf = 0u32;
for r in 1..h - 1 {
for c in 1..w - 1 {
let l = 4.0 * buf[r][c] - buf[r - 1][c] - buf[r + 1][c] - buf[r][c - 1] - buf[r][c + 1];
lap_full += l.abs();
nf += 1;
}
}
let lap_full = lap_full / nf as f32;
let (hh, ww) = (h / 2, w / 2);
if hh < 3 || ww < 3 {
return (mean, 0.0);
}
let mut half = [[0f32; 32]; 32];
for r in 0..hh {
for c in 0..ww {
half[r][c] = 0.25
* (buf[2 * r][2 * c]
+ buf[2 * r][2 * c + 1]
+ buf[2 * r + 1][2 * c]
+ buf[2 * r + 1][2 * c + 1]);
}
}
let mut lap_half = 0f32;
let mut nh = 0u32;
for r in 1..hh - 1 {
for c in 1..ww - 1 {
let l = 4.0 * half[r][c]
- half[r - 1][c]
- half[r + 1][c]
- half[r][c - 1]
- half[r][c + 1];
lap_half += l.abs();
nh += 1;
}
}
let lap_half = lap_half / nh as f32;
(mean, (lap_full * lap_half).sqrt())
}
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()
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub(crate) struct AqCell {
pub(crate) qs: i32,
pub(crate) resid_scale: f32,
pub(crate) qs_c: i32,
pub(crate) resid_scale_c: f32,
pub(crate) sig: i32,
pub(crate) qidx: i32,
pub(crate) qs_before: i32,
pub(crate) resid_scale_before: f32,
}
#[derive(Clone, Copy, Debug)]
pub struct DarkAq {
pub enabled: bool,
pub min_q: i32,
pub mean_floor: f32,
pub dark_ref: f32,
pub gamma: f32,
pub max_weight: f32,
pub scale: f32,
pub max_qidx: i32,
}
impl Default for DarkAq {
fn default() -> Self {
DarkAq {
enabled: false,
min_q: 150,
mean_floor: 16.0,
dark_ref: 56.0,
gamma: 1.2,
max_weight: 4.5,
scale: 4.0,
max_qidx: 16,
}
}
}
pub(crate) struct AqState {
present: bool,
base_q: i32,
qstep_base: i32,
uv_delta: i32,
ref_act: f32,
last_qidx: i32,
vb_octile: u8,
vb_strength: f32,
vb_boost_only: bool,
dark: DarkAq,
}
impl AqState {
pub(crate) fn new(
present: bool,
base_q: i32,
qstep_base: i32,
ref_act: f32,
uv_delta: i32,
) -> Self {
AqState {
present,
base_q,
qstep_base,
uv_delta,
ref_act,
last_qidx: base_q,
vb_octile: 6,
vb_strength: 1.0,
vb_boost_only: false,
dark: DarkAq::default(),
}
}
pub(crate) fn with_dark_aq(mut self, dark: DarkAq) -> Self {
self.dark = dark;
self
}
fn chroma_at(&self, q: i32) -> (i32, f32) {
let qc = qstep((q + self.uv_delta).clamp(1, 255) as u32) as i32;
(qc, self.qstep_base as f32 / qc as f32)
}
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
}
fn dark_protection(
&self,
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> i32 {
let d = &self.dark;
if !d.enabled || self.base_q < d.min_q {
return 0;
}
let (mean, mid_energy) = dark_structure_stats(yp, pw, sb_y, sb_x, width, height);
if mid_energy <= 0.0 {
return 0;
}
let dark_weight = (((d.mean_floor + d.dark_ref) / (d.mean_floor + mean)).powf(d.gamma))
.clamp(1.0, d.max_weight);
let darkness = dark_weight - 1.0;
if darkness <= 0.0 {
return 0;
}
let dark_structure = (mid_energy * darkness).ln_1p();
((dark_structure * d.scale).min(d.max_qidx as f32))
.max(0.0)
.fast_round() as i32
}
fn sb_target(
&self,
yp: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
width: usize,
height: usize,
) -> i32 {
let mut subvars = [0f32; 64];
let filled = sb_subblock_variances(yp, pw, sb_y, sb_x, width, height, &mut subvars);
if filled == 0 {
return self.base_q;
}
let picked = sb_octile_variance(&mut subvars, self.vb_octile);
let vb_delta =
variance_boost_delta(picked, self.ref_act, self.vb_strength, self.vb_boost_only);
let dark = self.dark_protection(yp, pw, sb_y, sb_x, width, height);
let flat_boost = (-vb_delta).max(0);
let protection = flat_boost.max(dark);
let delta = if protection > 0 {
-protection
} else {
vb_delta
};
(self.base_q + delta).clamp(1, 255)
}
pub(crate) fn current_qidx(&self) -> i32 {
self.last_qidx
}
#[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, i32, f32) {
if !self.present {
let (qc, rc) = self.chroma_at(self.base_q);
return (self.qstep_base, 1.0, qc, rc);
}
let target = self.sb_target(yp, pw, sb_y, sb_x, width, height);
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;
let (qc, rc) = self.chroma_at(newq);
(qs, self.qstep_base as f32 / qs as f32, qc, rc)
}
pub(crate) fn current(&self) -> (i32, f32, i32, f32) {
if !self.present {
let (qc, rc) = self.chroma_at(self.base_q);
return (self.qstep_base, 1.0, qc, rc);
}
let qs = qstep(self.last_qidx as u32) as i32;
let (qc, rc) = self.chroma_at(self.last_qidx);
(qs, self.qstep_base as f32 / qs as f32, qc, rc)
}
#[allow(dead_code)]
pub(crate) fn precompute_grid(
&self,
yp: &[f32],
pw: usize,
width: usize,
height: usize,
needs_partition: bool,
) -> Vec<AqCell> {
let sb_cols = width.div_ceil(64);
let sb_rows = height.div_ceil(64);
let step = 1i32 << AQ_RES_LOG2;
let mut last_qidx = self.last_qidx;
let mut grid = Vec::with_capacity(sb_cols * sb_rows);
for r in 0..sb_rows {
for c in 0..sb_cols {
let sb_y = r * 64;
let sb_x = c * 64;
let full_interior = (sb_x + 64 <= width && sb_y + 64 <= height) || !needs_partition;
let qs_before = if self.present {
qstep(last_qidx as u32) as i32
} else {
self.qstep_base
};
let resid_scale_before = self.qstep_base as f32 / qs_before as f32;
let cell = if !self.present {
let (qc, rc) = self.chroma_at(self.base_q);
AqCell {
qs: self.qstep_base,
resid_scale: 1.0,
qs_c: qc,
resid_scale_c: rc,
sig: 0,
qidx: self.base_q,
qs_before,
resid_scale_before,
}
} else if full_interior {
let target = self.sb_target(yp, pw, sb_y, sb_x, width, height);
let sig = (((target - last_qidx) as f32) / step as f32)
.fast_round()
.clamp(-(AQ_MAX_SIGNALED as f32), AQ_MAX_SIGNALED as f32)
as i32;
let newq = (last_qidx + sig * step).clamp(1, 255);
last_qidx = newq;
let qs = qstep(newq as u32) as i32;
let (qc, rc) = self.chroma_at(newq);
AqCell {
qs,
resid_scale: self.qstep_base as f32 / qs as f32,
qs_c: qc,
resid_scale_c: rc,
sig,
qidx: newq,
qs_before,
resid_scale_before,
}
} else {
let qs = qstep(last_qidx as u32) as i32;
let (qc, rc) = self.chroma_at(last_qidx);
AqCell {
qs,
resid_scale: self.qstep_base as f32 / qs as f32,
qs_c: qc,
resid_scale_c: rc,
sig: 0,
qidx: last_qidx,
qs_before,
resid_scale_before,
}
};
grid.push(cell);
}
}
grid
}
#[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, i32, f32) {
if !self.present {
enc.delta_q_signaled = 0;
let (qc, rc) = self.chroma_at(self.base_q);
return (self.qstep_base, 1.0, qc, rc);
}
let target = self.sb_target(yp, pw, sb_y, sb_x, width, height);
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;
let (qc, rc) = self.chroma_at(newq);
(qs, self.qstep_base as f32 / qs as f32, qc, rc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dark_protection_targets_dark_structure_only() {
let build = |mean: f32| -> Vec<f32> {
let mut p = vec![0f32; 64 * 64];
for r in 0..64 {
for c in 0..64 {
p[r * 64 + c] = mean + if (c / 4) % 2 == 0 { 12.0 } else { -12.0 };
}
}
p
};
let base_q = 190; let st =
AqState::new(true, base_q, qstep(base_q as u32) as i32, 10.0, 0).with_dark_aq(DarkAq {
enabled: true,
..DarkAq::default()
});
let dark = build(36.0);
let bright = build(180.0);
let flat_dark = vec![24.0f32; 64 * 64]; let d_dark = st.dark_protection(&dark, 64, 0, 0, 64, 64);
let d_bright = st.dark_protection(&bright, 64, 0, 0, 64, 64);
let d_flat = st.dark_protection(&flat_dark, 64, 0, 0, 64, 64);
assert!(
d_dark > 0,
"dark structured SB should be protected, got {d_dark}"
);
assert_eq!(d_bright, 0, "bright structured SB must not be protected");
assert_eq!(
d_flat, 0,
"dark flat SB (no structure) must not be protected"
);
let st_hi = AqState::new(true, 100, qstep(100) as i32, 10.0, 0).with_dark_aq(DarkAq {
enabled: true,
..DarkAq::default()
});
assert_eq!(
st_hi.dark_protection(&dark, 64, 0, 0, 64, 64),
0,
"gated out below min_q"
);
}
#[test]
fn precompute_grid_matches_serial_per_sb() {
let base_q = 120;
let base_step = qstep(base_q as u32) as i32;
let (width, height) = (160usize, 96usize);
let pw = crate::av2::helpers::sb_align(width);
let ph = crate::av2::helpers::sb_align(height);
let yp: Vec<f32> = (0..pw * ph).map(|i| (i * 37 % 251) as f32).collect();
let mk = || AqState::new(true, base_q, base_step, 10.0, 0);
let sb_cols = width.div_ceil(64);
let sb_rows = height.div_ceil(64);
for needs_partition in [true, false] {
let grid = mk().precompute_grid(&yp, pw, width, height, needs_partition);
let mut aq = mk();
let mut enc = crate::av2::entropy::RangeEncoder::new();
for r in 0..sb_rows {
for c in 0..sb_cols {
let (sb_y, sb_x) = (r * 64, c * 64);
let full_interior =
(sb_x + 64 <= width && sb_y + 64 <= height) || !needs_partition;
let (qs, rs, qc, rc) = if full_interior {
aq.per_sb(&mut enc, &yp, pw, sb_y, sb_x, width, height)
} else {
enc.delta_q_signaled = 0;
aq.current()
};
let cell = grid[r * sb_cols + c];
assert_eq!(cell.qs, qs, "qs at ({r},{c}) np={needs_partition}");
assert_eq!(cell.qs_c, qc, "qs_c at ({r},{c}) np={needs_partition}");
assert_eq!(cell.sig, enc.delta_q_signaled, "sig at ({r},{c})");
assert_eq!(cell.resid_scale.to_bits(), rs.to_bits(), "rs at ({r},{c})");
assert_eq!(cell.resid_scale_c.to_bits(), rc.to_bits(), "rs_c ({r},{c})");
assert_eq!(cell.qidx, aq.current_qidx(), "qidx at ({r},{c})");
}
}
}
}
#[test]
fn probe_does_not_advance_delta_q_accumulator() {
let base_q = 120;
let base_step = qstep(base_q as u32) as i32;
let mut aq =
AqState::new(true, base_q, base_step, 10.0, 0).with_variance_boost(6, 1.0, true);
let plane = vec![128.0f32; 64 * 64];
let before = aq.current();
let probed = aq.per_sb_probe(&plane, 64, 0, 0, 64, 64);
assert_ne!(probed.0, before.0, "flat block should request an AQ boost");
assert_eq!(
aq.current().0,
before.0,
"probe must preserve decoder qindex state"
);
let mut enc = RangeEncoder::new();
enc.delta_q_present = true;
let committed = aq.per_sb(&mut enc, &plane, 64, 0, 0, 64, 64);
assert_eq!(committed.0, probed.0);
assert_eq!(aq.current().0, probed.0);
assert_ne!(enc.delta_q_signaled, 0);
}
}