#![allow(clippy::needless_range_loop)]
use crate::cabac::CabacEncoder;
use crate::config::EncoderConfig;
use rusty_h264_common::cavlc::{
encode_residual_block, scan_4x4_ac, scan_4x4_dcac, write_cbp_inter, write_cbp_intra,
};
use rusty_h264_common::inter::{
inter_partitions, mc_chroma, mc_luma, predict_mv, predict_partition_mv, MvNeighbor,
};
use rusty_h264_common::predict::{
add_residual_4x4, add_residual_8x8, chroma8x8_pred, chroma_mode_available, chroma_qp,
intra4x4_pred, intra8x8_pred, luma16x16_pred, reconstruct_4x4, I16Mode, CHROMA_4X4_SCAN_XY,
LUMA_4X4_SCAN_XY,
};
use rusty_h264_common::transform::{
dequantize, forward_core, forward_core_8x8, forward_dct_blocks, forward_quant_chroma_dc,
forward_quant_luma_dc, inverse_dct_blocks, inverse_quant_8x8, inverse_quant_chroma_dc,
inverse_quant_luma_dc, quantize, quantize_8x8, satd_4x4_sum,
};
use rusty_h264_common::aligned::AlignedBytes;
use rusty_h264_common::{BitWriter, YuvFrame};
pub(crate) static DEFER_SUBPEL: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
pub(crate) static SPLIT_T: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
fn split_t() -> f64 {
let v = SPLIT_T.load(std::sync::atomic::Ordering::Relaxed);
if v != u32::MAX {
return v as f64;
}
let d: u32 = std::env::var("RFF_SPLIT_T").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
SPLIT_T.store(d, std::sync::atomic::Ordering::Relaxed);
d as f64
}
mod split_harvest {
use std::fs::File;
use std::io::Write;
use std::sync::{Mutex, OnceLock};
fn sink() -> &'static Option<Mutex<File>> {
static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
S.get_or_init(|| {
std::env::var("RFF_SPLIT_HARVEST").ok().and_then(|p| {
let mut f = File::create(p).ok()?;
let _ = writeln!(f, "c16,best,lambda,gate,won");
Some(Mutex::new(f))
})
})
}
#[inline]
pub fn enabled() -> bool {
sink().is_some()
}
pub fn record(c16: i64, best: i64, lambda: f64, gate: i64, won: u8) {
if let Some(m) = sink() {
if let Ok(mut f) = m.lock() {
let _ = writeln!(f, "{c16},{best},{lambda:.4},{gate},{won}");
}
}
}
}
fn hpel_ref_enabled() -> bool {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_HPEL_REF").map(|v| v != "0").unwrap_or(true))
}
static MV_COST_TAB: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
fn build_mv_cost() -> Vec<u16> {
(0..4096u32)
.map(|a| {
let c = 2.0 * ((a + 1) as f64).log2() + 0.718 + if a != 0 { 1.0 } else { 0.0 };
(c * 4.0).round() as u16
})
.collect()
}
static MV_SMOOTH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_mv_smooth(on: bool) {
MV_SMOOTH.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
}
pub fn set_mv_smooth_mode(m: u32) {
MV_SMOOTH.store(m.min(3), core::sync::atomic::Ordering::Relaxed)
}
static MV_TRUE_BIASED: std::sync::OnceLock<Vec<u16>> = std::sync::OnceLock::new();
fn build_true_biased() -> Vec<u16> {
let bias_q4 = (std::env::var("RFF_MVCOST_BIAS")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(1.0)
* 4.0)
.round() as u16;
crate::mvd_cost_tab::MVD_TRUE_COST4
.iter()
.enumerate()
.map(|(d, &c)| if d == 0 { c } else { c.saturating_add(bias_q4) })
.collect()
}
fn mv_smooth_t() -> f64 {
static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*T.get_or_init(|| std::env::var("RFF_MVCOST_T").ok().and_then(|v| v.parse().ok()).unwrap_or(0.10))
}
#[inline]
fn mv_cost_kind(frame_smooth: bool) -> u32 {
match mv_smooth_mode() {
0 => 0,
1 => frame_smooth as u32,
2 => 1, _ => 2, }
}
#[inline]
fn mv_smooth_mode() -> u32 {
match MV_SMOOTH.load(core::sync::atomic::Ordering::Relaxed) {
m @ 0..=3 => m,
_ => {
static E: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
*E.get_or_init(|| {
std::env::var("RFF_MVCOST").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
})
}
}
}
fn mectx_enabled() -> bool {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_MECTX").map(|v| v != "0").unwrap_or(true))
}
fn satd_avg_enabled() -> bool {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_SATD_AVG").map(|v| v != "0").unwrap_or(true))
}
static ME_SADFP: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_me_sadfp(on: bool) {
ME_SADFP.store(if on { 2 } else { 0 }, core::sync::atomic::Ordering::Relaxed)
}
pub fn set_me_sadfp_mode(m: u32) {
ME_SADFP.store(m.min(2), core::sync::atomic::Ordering::Relaxed)
}
fn me_sadfp_mode() -> u32 {
match ME_SADFP.load(core::sync::atomic::Ordering::Relaxed) {
u32::MAX => {
static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
*INIT.get_or_init(|| {
std::env::var("RFF_ME_SADFP").ok().and_then(|v| v.parse().ok()).unwrap_or(1)
})
}
m => m,
}
}
fn me_sadt() -> f64 {
static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*T.get_or_init(|| std::env::var("RFF_ME_SADT").ok().and_then(|s| s.parse().ok()).unwrap_or(0.13))
}
fn me_sadt_dbg() -> bool {
static D: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*D.get_or_init(|| std::env::var_os("RFF_ME_SADT_DBG").is_some())
}
static SP_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_sp_fc(on: bool) {
SP_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
}
fn sp_fc_enabled() -> bool {
match SP_FC.load(core::sync::atomic::Ordering::Relaxed) {
0 => false,
1 => true,
_ => {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_SP_FC").map(|v| v != "0").unwrap_or(false))
}
}
}
static ME_FC: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_me_fc(on: bool) {
ME_FC.store(on as u32, core::sync::atomic::Ordering::Relaxed)
}
fn me_fc_enabled() -> bool {
match ME_FC.load(core::sync::atomic::Ordering::Relaxed) {
0 => false,
1 => true,
_ => {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_ME_FC").map(|v| v != "0").unwrap_or(true))
}
}
}
static SPLIT_MG: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_split_mg(milli: u32) {
SPLIT_MG.store(milli, core::sync::atomic::Ordering::Relaxed)
}
fn split_mg() -> f64 {
match SPLIT_MG.load(core::sync::atomic::Ordering::Relaxed) {
u32::MAX => {
static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*E.get_or_init(|| {
std::env::var("RFF_SPLIT_MG").ok().and_then(|v| v.parse().ok()).unwrap_or(0.0)
})
}
m => m as f64 / 1000.0,
}
}
fn me_sad_dcmax() -> f64 {
static T: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*T.get_or_init(|| std::env::var("RFF_ME_SADDC").ok().and_then(|s| s.parse().ok()).unwrap_or(0.6))
}
fn b2_mgain(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> (f64, f64) {
const WIDE: isize = 8;
const STEP: isize = 4;
const TARGET: usize = 24;
let sad16 = |bx: usize, by: usize, rx: isize, ry: isize| -> Option<u32> {
if rx < 0 || ry < 0 || rx as usize + 16 > cw || ry as usize + 16 > ch {
return None;
}
let (rx, ry) = (rx as usize, ry as usize);
let mut s = 0u32;
for dy in 0..16 {
let a = &sy[(by + dy) * cw + bx..][..16];
let b = &ref_y[(ry + dy) * cw + rx..][..16];
s += a.iter().zip(b).map(|(&p, &q)| p.abs_diff(q) as u32).sum::<u32>();
}
Some(s)
};
let (mbw, mbh) = (cw / 16, ch / 16);
if mbw < 6 || mbh < 6 {
return (0.0, 0.0);
}
let inner = (mbw - 4) * (mbh - 4);
let stride = (inner / TARGET).max(1);
let (mut acc, mut dc, mut n) = (0.0f64, 0.0f64, 0u32);
let mut i = 0usize;
while i < inner {
let (mx, my) = (2 + i % (mbw - 4), 2 + i / (mbw - 4));
let (bx, by) = (mx * 16, my * 16);
if let Some(s0) = sad16(bx, by, bx as isize, by as isize) {
let (mut ms, mut mr) = (0u32, 0u32);
for dy in 0..16 {
ms += sy[(by + dy) * cw + bx..][..16].iter().map(|&v| v as u32).sum::<u32>();
mr += ref_y[(by + dy) * cw + bx..][..16].iter().map(|&v| v as u32).sum::<u32>();
}
dc += ms.abs_diff(mr) as f64 / (s0 + 1) as f64;
let mut best = s0;
let mut dy = -WIDE;
while dy <= WIDE {
let mut dx = -WIDE;
while dx <= WIDE {
if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
best = best.min(s);
}
dx += STEP;
}
dy += STEP;
}
acc += (s0 - best) as f64 / (s0 + 1) as f64;
n += 1;
}
i += stride;
}
if n == 0 { (0.0, 0.0) } else { (acc / n as f64, dc / n as f64) }
}
static SP_MAXIT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_sp_maxit(n: u32) {
SP_MAXIT.store(n, core::sync::atomic::Ordering::Relaxed)
}
fn sp_maxit() -> u32 {
match SP_MAXIT.load(core::sync::atomic::Ordering::Relaxed) {
u32::MAX => {
static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
*INIT.get_or_init(|| {
std::env::var("RFF_SP_MAXIT").ok().and_then(|v| v.parse().ok()).unwrap_or(0)
})
}
n => n,
}
}
fn me_sadfp_lambda() -> f64 {
static E: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*E.get_or_init(|| {
std::env::var("RFF_ME_SADL").ok().and_then(|v| v.parse().ok()).unwrap_or(0.5)
})
}
#[cfg(feature = "profile")]
pub mod spstats {
use core::sync::atomic::{AtomicU64, Ordering};
pub static POS: [AtomicU64; 2 * 8 * 2] = [const { AtomicU64::new(0) }; 32];
pub static IT: [AtomicU64; 2 * 6 * 2] = [const { AtomicU64::new(0) }; 24];
#[inline]
pub fn ev(st: usize, pos: usize, it: u32) {
POS[(st * 8 + pos.min(7)) * 2].fetch_add(1, Ordering::Relaxed);
IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2].fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn imp(st: usize, pos: usize, it: u32) {
POS[(st * 8 + pos.min(7)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
IT[(st * 6 + (it.max(1) as usize - 1).min(5)) * 2 + 1].fetch_add(1, Ordering::Relaxed);
}
pub static REDUNDANT: AtomicU64 = AtomicU64::new(0);
#[inline]
pub fn redundant() { REDUNDANT.fetch_add(1, Ordering::Relaxed); }
pub fn reset() {
for c in POS.iter() { c.store(0, Ordering::Relaxed); }
for c in IT.iter() { c.store(0, Ordering::Relaxed); }
REDUNDANT.store(0, Ordering::Relaxed);
}
pub fn snapshot() -> (Vec<u64>, Vec<u64>) {
(POS.iter().map(|c| c.load(Ordering::Relaxed)).collect(),
IT.iter().map(|c| c.load(Ordering::Relaxed)).collect())
}
pub fn redundant_count() -> u64 { REDUNDANT.load(Ordering::Relaxed) }
}
#[cfg(feature = "profile")]
pub mod satdpath {
use core::sync::atomic::{AtomicU64, Ordering};
pub static C: [AtomicU64; 3] = [const { AtomicU64::new(0) }; 3];
#[inline]
pub fn bump(i: usize) { C[i].fetch_add(1, Ordering::Relaxed); }
pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
pub fn snapshot() -> Vec<u64> { C.iter().map(|c| c.load(Ordering::Relaxed)).collect() }
}
pub const DIA_RUNGS: [i32; 5] = [64, 32, 16, 8, 4];
pub const DIA_DEFAULT: u32 = 0b11100;
pub static DIA_MASK: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
pub fn set_dia_mask(m: u32) { DIA_MASK.store(m, core::sync::atomic::Ordering::Relaxed) }
fn dia_mask() -> u32 {
let m = DIA_MASK.load(core::sync::atomic::Ordering::Relaxed);
if m != u32::MAX { return m; }
static INIT: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
*INIT.get_or_init(|| match std::env::var("RFF_DIA_LADDER") {
Ok(v) => {
let want: Vec<i32> = v.split(',').filter_map(|t| t.trim().parse().ok()).collect();
let mut m = 0u32;
for (i, r) in DIA_RUNGS.iter().enumerate() {
if want.contains(r) { m |= 1 << i; }
}
if m == 0 { DIA_DEFAULT } else { m }
}
Err(_) => DIA_DEFAULT,
})
}
#[cfg(feature = "profile")]
pub mod diastats {
use core::sync::atomic::{AtomicU64, Ordering};
pub static C: [AtomicU64; 12] = [const { AtomicU64::new(0) }; 12];
#[inline]
pub fn ev(i: usize) { C[i * 2].fetch_add(1, Ordering::Relaxed); }
#[inline]
pub fn imp(i: usize) { C[i * 2 + 1].fetch_add(1, Ordering::Relaxed); }
pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
pub fn snapshot() -> Vec<(u64, u64)> {
(0..6).map(|i| (C[i * 2].load(Ordering::Relaxed), C[i * 2 + 1].load(Ordering::Relaxed))).collect()
}
}
pub(crate) static SUBPEL_PAT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
pub(crate) static SP_DISPATCH: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
fn sp_dispatch_cfg() -> (u32, i64) {
use std::sync::OnceLock;
let forced = SP_DISPATCH.load(std::sync::atomic::Ordering::Relaxed);
if forced == 0 {
return (0, 0);
}
static C: OnceLock<(u32, i64)> = OnceLock::new();
*C.get_or_init(|| {
let on = std::env::var("RFF_SUBPEL_DISPATCH").map(|s| s != "0").unwrap_or(false);
if !on {
return (0, 0);
}
let k = std::env::var("RFF_SUBPEL_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(200);
let t = std::env::var("RFF_SUBPEL_T").ok().and_then(|s| s.parse().ok()).unwrap_or(67);
(k, t)
})
}
fn subpel_pattern_override() -> Option<u32> {
let v = SUBPEL_PAT.load(std::sync::atomic::Ordering::Relaxed);
if v != u32::MAX {
return Some(v);
}
if let Some(e) = std::env::var("RFF_SUBPEL_PAT").ok().and_then(|s| s.parse::<u32>().ok()) {
SUBPEL_PAT.store(e, std::sync::atomic::Ordering::Relaxed);
return Some(e);
}
None
}
fn subpel_pattern() -> u32 {
let v = SUBPEL_PAT.load(std::sync::atomic::Ordering::Relaxed);
if v != u32::MAX {
return v;
}
let d = std::env::var("RFF_SUBPEL_PAT").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
SUBPEL_PAT.store(d, std::sync::atomic::Ordering::Relaxed);
d
}
mod subpel_harvest {
use std::fs::File;
use std::io::Write;
use std::sync::{Mutex, OnceLock};
fn sink() -> &'static Option<Mutex<File>> {
static S: OnceLock<Option<Mutex<File>>> = OnceLock::new();
S.get_or_init(|| {
std::env::var("RFF_SUBPEL_HARVEST").ok().and_then(|p| {
let mut f = File::create(p).ok()?;
let _ = writeln!(f, "pre,post,lambda,w,h,evals,to_best,ring1");
Some(Mutex::new(f))
})
})
}
#[inline]
pub fn enabled() -> bool {
sink().is_some()
}
#[allow(clippy::too_many_arguments)]
pub fn record(pre: i64, post: i64, lambda: f64, w: usize, h: usize, evals: u32, to_best: u32, ring1: i64) {
if let Some(m) = sink() {
if let Ok(mut f) = m.lock() {
let _ = writeln!(f, "{pre},{post},{lambda:.4},{w},{h},{evals},{to_best},{ring1}");
}
}
}
}
fn bdirect_planes_enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_BDIRECT_PLANES").map(|s| s != "0").unwrap_or(true))
}
fn me_batch_enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_ME_BATCH").map(|s| s != "0").unwrap_or(true))
}
#[cfg(accel)]
#[repr(align(16))]
struct AlignedMb([u8; 256]);
#[derive(Clone, Copy)]
struct BInter<'a> {
dir: u8,
l1: &'a crate::RefFrame,
mv0: (i32, i32),
mv1: (i32, i32),
}
#[cfg(accel)]
#[repr(align(16))]
struct AlignedDct([i16; 256]);
fn mb_variance(sy: &[u8], cw: usize, mb_x: usize, mb_y: usize) -> i64 {
let base = mb_y * 16 * cw + mb_x * 16;
let (mut s, mut ss) = (0u32, 0u32);
for r in 0..16 {
let row = &sy[base + r * cw..base + r * cw + 16];
for &p in row {
let v = p as u32;
s += v;
ss += v * v;
}
}
ss as i64 - (s as i64) * (s as i64) / 256 }
fn aq_qp_map(sy: &[u8], cw: usize, mb_w: usize, mb_h: usize, base_qp: u8, strength: f64) -> Vec<u8> {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncAq);
const AQ_DQP_MAX: i32 = 4;
let n = mb_w * mb_h;
if strength == 0.0 || n == 0 {
return vec![base_qp; n];
}
let mut var = Vec::with_capacity(n);
let mut lv = Vec::with_capacity(n);
for my in 0..mb_h {
for mx in 0..mb_w {
let v = (mb_variance(sy, cw, mx, my) + 1) as f64;
var.push(v);
lv.push(v.log2());
}
}
let mean_lv = lv.iter().sum::<f64>() / n as f64;
const AQ_SPREAD_LO: f64 = 1.5;
const AQ_SPREAD_HI: f64 = 5.0;
const AQ_SPREAD_MIN: f64 = 0.0; let std_lv = (lv.iter().map(|&l| (l - mean_lv).powi(2)).sum::<f64>() / n as f64).sqrt();
let factor = (1.0 - (std_lv - AQ_SPREAD_LO) / (AQ_SPREAD_HI - AQ_SPREAD_LO)).clamp(AQ_SPREAD_MIN, 1.0);
let eff_strength = strength * factor;
let dqp: Vec<i32> = lv
.iter()
.map(|&l| (eff_strength * (l - mean_lv)).round() as i32)
.map(|d| d.clamp(-AQ_DQP_MAX, AQ_DQP_MAX))
.collect();
let sum_v: f64 = var.iter().sum();
let qstep: [f64; (2 * AQ_DQP_MAX + 1) as usize] =
std::array::from_fn(|i| 2f64.powf(-((i as i32 - AQ_DQP_MAX) as f64) / 6.0));
let sum_vs: f64 = var
.iter()
.zip(&dqp)
.map(|(&v, &d)| v * qstep[(d + AQ_DQP_MAX) as usize])
.sum();
let c = (6.0 * (sum_vs / sum_v).log2()).round() as i32;
dqp.iter()
.map(|&d| (base_qp as i32 + c + d).clamp(0, 51) as u8)
.collect()
}
fn me_wide_hr_thresh() -> f64 {
use std::sync::OnceLock;
static T: OnceLock<f64> = OnceLock::new();
*T.get_or_init(|| std::env::var("RFF_ME_HR").ok().and_then(|s| s.parse().ok()).unwrap_or(16.0))
}
fn me_wide_hr_dbg() -> bool {
use std::sync::OnceLock;
static D: OnceLock<bool> = OnceLock::new();
*D.get_or_init(|| std::env::var_os("RFF_ME_HR_DBG").is_some())
}
fn me_wide_headroom(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> f64 {
const LOCAL: isize = 2; const WIDE: isize = 24; const STEP: isize = 4; const TARGET: usize = 24; let sad16 = |bx: usize, by: usize, rx: isize, ry: isize| -> Option<u32> {
if rx < 0 || ry < 0 || rx as usize + 16 > cw || ry as usize + 16 > ch {
return None;
}
let (rx, ry) = (rx as usize, ry as usize);
let mut s = 0u32;
for dy in 0..16 {
let a = &sy[(by + dy) * cw + bx..][..16];
let b = &ref_y[(ry + dy) * cw + rx..][..16];
s += a.iter().zip(b).map(|(&p, &q)| p.abs_diff(q) as u32).sum::<u32>();
}
Some(s)
};
let (mbw, mbh) = (cw / 16, ch / 16);
if mbw < 6 || mbh < 6 {
return 0.0;
}
let inner = (mbw - 4) * (mbh - 4);
let stride = (inner / TARGET).max(1);
let (mut acc, mut n) = (0.0f64, 0u32);
let mut i = 0usize;
while i < inner {
let (mx, my) = (2 + i % (mbw - 4), 2 + i / (mbw - 4));
let (bx, by) = (mx * 16, my * 16);
let mut best_local = u32::MAX;
for dy in -LOCAL..=LOCAL {
for dx in -LOCAL..=LOCAL {
if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
best_local = best_local.min(s);
}
}
}
let mut best_wide = best_local;
let mut dy = -WIDE;
while dy <= WIDE {
let mut dx = -WIDE;
while dx <= WIDE {
if let Some(s) = sad16(bx, by, bx as isize + dx, by as isize + dy) {
best_wide = best_wide.min(s);
}
dx += STEP;
}
dy += STEP;
}
if best_local > 0 {
acc += (best_local - best_wide) as f64 / best_local as f64;
n += 1;
}
i += stride;
}
if n == 0 {
0.0
} else {
100.0 * acc / n as f64
}
}
fn global_mc_residual(sy: &[u8], cw: usize, ch: usize, ref_y: &[u8]) -> f64 {
if cw < 48 || ch < 48 {
return f64::INFINITY;
}
let sad = |dx: isize, dy: isize| -> u64 {
let mut s = 0u64;
let mut y = 16;
while y < ch - 16 {
let cbase = (y * cw) as isize;
let rbase = (y as isize + dy) * cw as isize + dx;
let mut x = 16isize;
while x < (cw - 16) as isize {
let c = sy[(cbase + x) as usize] as i32;
let r = ref_y[(rbase + x) as usize] as i32;
s += (c - r).unsigned_abs() as u64;
x += 8;
}
y += 8;
}
s
};
let (mut best, mut bc) = ((0isize, 0isize), u64::MAX);
let mut dy = -12;
while dy <= 12 {
let mut dx = -12;
while dx <= 12 {
let c = sad(dx, dy);
if c < bc {
bc = c;
best = (dx, dy);
}
dx += 4;
}
dy += 4;
}
for dy in best.1 - 3..=best.1 + 3 {
for dx in best.0 - 3..=best.0 + 3 {
let c = sad(dx, dy);
if c < bc {
bc = c;
}
}
}
let nx = (16..cw - 16).step_by(8).count();
let ny = (16..ch - 16).step_by(8).count();
bc as f64 / (nx * ny).max(1) as f64
}
fn apply_mbtree_qpo(aq_qp: &mut [u8], qpo: &[i32]) {
if qpo.len() == aq_qp.len() {
for (q, &o) in aq_qp.iter_mut().zip(qpo) {
*q = (*q as i32 + o).clamp(0, 51) as u8;
}
}
}
fn implicit_bi_weights(cur_poc: i32, l0_poc: i32, l1_poc: i32) -> (i32, i32) {
let td = (l1_poc - l0_poc).clamp(-128, 127);
let tb = (cur_poc - l0_poc).clamp(-128, 127);
if td == 0 {
return (32, 32);
}
let tx = (16384 + td.abs() / 2) / td;
let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
let w1 = dsf >> 2;
if !(-64..=128).contains(&w1) {
return (32, 32);
}
(64 - w1, w1)
}
#[inline(always)]
fn bi_blend(p: i32, q: i32, w: (i32, i32)) -> u8 {
((p * w.0 + q * w.1 + 32) >> 6).clamp(0, 255) as u8
}
#[cfg(accel)]
#[inline]
fn scan_4x4_dcac_i16(d: &[i16]) -> [i32; 16] {
[
d[0] as i32, d[1] as i32, d[4] as i32, d[8] as i32, d[5] as i32, d[2] as i32,
d[3] as i32, d[6] as i32, d[9] as i32, d[12] as i32, d[13] as i32, d[10] as i32,
d[7] as i32, d[11] as i32, d[14] as i32, d[15] as i32,
]
}
pub struct FrameEncoder {
mb_w: usize,
mb_h: usize,
qp: u8, qpc: u8, cur_qp: u8,
bi_w: (i32, i32),
cw: usize, ccw: usize, rec_y: AlignedBytes,
rec_u: AlignedBytes,
rec_v: AlignedBytes,
nnz_y: Vec<u8>, nnz_c: [Vec<u8>; 2], modes_y: Vec<u8>, coded_y: Vec<bool>, mv_y: Vec<(i32, i32)>, inter_y: Vec<bool>, ref_idx_y: Vec<i32>, mv1_y: Vec<(i32, i32)>,
ref_idx1_y: Vec<i32>,
idz: i64, rdoq_strength: f64, transform_8x8: bool, sub8x8: bool, me_wide: bool, sadfp: bool,
mv_smooth: bool,
do_splits: bool,
me_wide_var: u64, me_rescue: i64, me_wide_coh: f64, me_range: i32, me_fast: bool, me_learn: u32,
me_payoff_pct: u32,
sp_single_pass: bool,
sp_defer: std::cell::Cell<bool>,
sp_learn_n: std::cell::Cell<u32>,
sp_ring1: std::cell::Cell<i64>,
sp_total: std::cell::Cell<i64>,
sp_1pass: std::cell::Cell<bool>,
resc_n: std::cell::Cell<u32>, resc_big: std::cell::Cell<u32>, resc_off: std::cell::Cell<bool>, inter8x8: u8, inter8_pen: i64, fast: bool, skip_accel_check: bool, coded_path_v2: bool, tune_lambda_scale: f64, tune_intra_penalty: f64,
satd_q: f64, subpel_force: bool, me_snap: bool, me_subpel_iter: bool, greedy_skip: bool, greedy_min_free: u32, rd_skip: bool, rd_skip_min_free: u32, rd_skip_fast_t: f64, satd_var_thresh: i64, aq_strength: f64, mb_use_satd: bool, nnz_l_cache: [u8; 25],
nnz_c_cache: [[u8; 9]; 2],
mb_skip_sad: Vec<u32>,
mb_was_skip: Vec<bool>,
}
type InterChoice = (u8, Vec<(i32, (i32, i32))>);
const SKIP_RATE_BITS: f64 = 1.0;
pub static EXT_MV: std::sync::Mutex<Vec<Vec<(i32, i32)>>> = std::sync::Mutex::new(Vec::new());
pub static MVCMP: [std::sync::atomic::AtomicU64; 7] = {
const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
[Z; 7]
};
pub static MVCMP_FRAME: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
fn mv_force_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_MV_FORCE").map_or(false, |v| v != "0"))
}
fn mv_cmp_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_MV_CMP").map_or(false, |v| v != "0"))
}
pub static MC_COUNT: [std::sync::atomic::AtomicU64; 2] = [
std::sync::atomic::AtomicU64::new(0),
std::sync::atomic::AtomicU64::new(0),
];
fn mc_count_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_MC_COUNT").map_or(false, |v| v != "0"))
}
pub static ME_PROBE: [std::sync::atomic::AtomicU64; 7] = {
const Z: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
[Z; 7]
};
fn me_oracle_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("RFF_ME_ORACLE").map_or(false, |v| v != "0"))
}
const SPLIT_GATE_BITS: f64 = 60.0;
const FAST_INTRA_PENALTY_BITS: f64 = 24.0;
#[derive(Default)]
struct MbState {
rec_y: Vec<u8>,
rec_u: Vec<u8>,
rec_v: Vec<u8>,
nnz_y: Vec<u8>,
nnz_c: [Vec<u8>; 2],
mv_y: Vec<(i32, i32)>,
inter_y: Vec<bool>,
ref_idx_y: Vec<i32>,
coded_y: Vec<bool>,
modes_y: Vec<u8>,
cur_qp: u8,
}
fn fast_intra_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("RUSTY_FAST_INTRA").map_or(true, |v| v != "0"))
}
fn coded_source(cfg: &EncoderConfig, frame: &YuvFrame) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSource);
let cw = cfg.mb_width() * 16;
let ch = cfg.mb_height() * 16;
if frame.width == cw && frame.height == ch {
return (frame.y.clone(), frame.u.clone(), frame.v.clone());
}
let y = clamp_plane(&frame.y, frame.width, frame.height, cw, ch);
let u = clamp_plane(&frame.u, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
let v = clamp_plane(&frame.v, frame.chroma_width(), frame.chroma_height(), cw / 2, ch / 2);
(y, u, v)
}
fn clamp_plane(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
let mut out = vec![0u8; ow * oh];
for y in 0..oh {
let sy = y.min(h - 1);
let src = &plane[sy * w..sy * w + w];
let dst = &mut out[y * ow..y * ow + ow];
if ow <= w {
dst.copy_from_slice(&src[..ow]);
} else {
dst[..w].copy_from_slice(src);
dst[w..].fill(src[w - 1]);
}
}
out
}
#[cfg(test)]
fn clamp_plane_per_pixel(plane: &[u8], w: usize, h: usize, ow: usize, oh: usize) -> Vec<u8> {
let mut out = vec![0u8; ow * oh];
for y in 0..oh {
for x in 0..ow {
out[y * ow + x] = plane[y.min(h - 1) * w + x.min(w - 1)];
}
}
out
}
#[cfg(test)]
mod source_tests {
use super::*;
#[test]
fn clamp_plane_matches_per_pixel_oracle() {
let mut s: u32 = 0xDEAD_BEEF;
let mut rnd = || {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(s >> 24) as u8
};
let cases = [
(1920usize, 1080usize, 1920usize, 1088usize), (960, 540, 960, 544), (352, 288, 352, 288), (100, 100, 112, 112), (37, 5, 48, 16), (16, 1, 16, 16), (1, 1, 16, 16), ];
for (w, h, ow, oh) in cases {
let plane: Vec<u8> = (0..w * h).map(|_| rnd()).collect();
assert_eq!(
clamp_plane(&plane, w, h, ow, oh),
clamp_plane_per_pixel(&plane, w, h, ow, oh),
"clamp mismatch for {w}x{h} -> {ow}x{oh}"
);
}
}
}
impl FrameEncoder {
fn new(cfg: &EncoderConfig) -> Self {
let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
let (cw, ch) = (mb_w * 16, mb_h * 16);
let (ccw, cch) = (cw / 2, ch / 2);
Self {
mb_w,
mb_h,
qp: cfg.qp,
qpc: chroma_qp(cfg.qp),
cur_qp: cfg.qp,
bi_w: (32, 32),
cw,
ccw,
rec_y: AlignedBytes::zeroed(cw * ch),
rec_u: AlignedBytes::zeroed(ccw * cch),
rec_v: AlignedBytes::zeroed(ccw * cch),
nnz_y: vec![0; (mb_w * 4) * (mb_h * 4)],
nnz_c: [vec![0; (mb_w * 2) * (mb_h * 2)], vec![0; (mb_w * 2) * (mb_h * 2)]],
modes_y: vec![2; (mb_w * 4) * (mb_h * 4)],
coded_y: vec![false; (mb_w * 4) * (mb_h * 4)],
mv_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
inter_y: vec![false; (mb_w * 4) * (mb_h * 4)],
ref_idx_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
mv1_y: vec![(0, 0); (mb_w * 4) * (mb_h * 4)],
ref_idx1_y: vec![-1; (mb_w * 4) * (mb_h * 4)],
idz: if cfg.gop_size <= 1 { 2 } else { 3 },
rdoq_strength: 0.0, transform_8x8: cfg.transform_8x8,
sub8x8: std::env::var("RFF_SUB8X8").ok().map(|s| s == "1")
.or(cfg.sub_8x8)
.unwrap_or(cfg.preset == crate::config::Preset::Quality),
sadfp: me_sadfp_mode() == 2,
mv_smooth: false,
do_splits: true,
me_wide: std::env::var("RFF_ME_WIDE").ok().map(|s| s == "1")
.or(cfg.me_wide)
.unwrap_or(cfg.preset == crate::config::Preset::Quality),
me_wide_var: std::env::var("RFF_ME_WIDE_VAR").ok().and_then(|s| s.parse().ok()).unwrap_or(800),
me_rescue: std::env::var("RFF_ME_RESCUE").ok().and_then(|s| s.parse().ok()).unwrap_or(3),
me_wide_coh: std::env::var("RFF_ME_COH").ok().and_then(|s| s.parse().ok()).unwrap_or(4.0),
me_range: std::env::var("RFF_ME_RANGE").ok().and_then(|s| s.parse().ok()).unwrap_or(24),
me_fast: std::env::var("RFF_ME_FASTMO").map(|s| s != "0").unwrap_or(true),
me_learn: std::env::var("RFF_ME_LEARN").ok().and_then(|s| s.parse().ok()).unwrap_or(40),
me_payoff_pct: std::env::var("RFF_ME_PAYOFF").ok().and_then(|s| s.parse().ok()).unwrap_or(15),
sp_single_pass: cfg.preset == crate::config::Preset::Balanced,
sp_defer: std::cell::Cell::new({
let a = DEFER_SUBPEL.load(std::sync::atomic::Ordering::Relaxed) != 0
|| std::env::var("RFF_DEFER_SUBPEL").map(|v| v != "0").unwrap_or(false);
a && cfg.preset == crate::config::Preset::Quality
}),
sp_learn_n: std::cell::Cell::new(0),
sp_ring1: std::cell::Cell::new(0),
sp_total: std::cell::Cell::new(0),
sp_1pass: std::cell::Cell::new(false),
resc_n: std::cell::Cell::new(0),
resc_big: std::cell::Cell::new(0),
resc_off: std::cell::Cell::new(false),
inter8x8: std::env::var("RFF_INTER8")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1),
inter8_pen: std::env::var("RFF_INTER8_PEN")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(8),
fast: cfg.preset != crate::config::Preset::Quality,
skip_accel_check: cfg.tune_skip_accel_check,
coded_path_v2: cfg.coded_path_v2,
aq_strength: cfg.aq_strength,
tune_lambda_scale: cfg.tune_lambda_scale,
tune_intra_penalty: cfg.tune_intra_penalty,
satd_q: cfg.tune_satd_q,
subpel_force: cfg.tune_subpel || cfg.preset == crate::config::Preset::Balanced,
me_snap: cfg.tune_me_snap,
me_subpel_iter: cfg.tune_me_subpel_iter,
greedy_skip: cfg.tune_greedy_skip,
greedy_min_free: cfg.tune_greedy_skip_min_free.unwrap_or(85),
rd_skip: cfg.tune_rd_skip,
rd_skip_fast_t: cfg.tune_rd_skip_fast_t.unwrap_or(0.0),
rd_skip_min_free: cfg.tune_rd_skip_min_free.unwrap_or(
if cfg.preset == crate::config::Preset::Fast { 60 } else { 90 },
),
satd_var_thresh: i64::MAX,
mb_use_satd: false,
nnz_l_cache: [0x80; 25],
nnz_c_cache: [[0x80; 9]; 2],
mb_skip_sad: vec![0; mb_w * mb_h],
mb_was_skip: vec![false; mb_w * mb_h],
}
}
fn pred_skip_sad(&self, mb_x: usize, mb_y: usize) -> u32 {
let mbw = self.mb_w;
let at = |x: isize, y: isize| -> Option<(bool, u32)> {
if x < 0 || y < 0 || x >= mbw as isize {
return None;
}
let i = y as usize * mbw + x as usize;
Some((self.mb_was_skip[i], self.mb_skip_sad[i]))
};
let a = at(mb_x as isize - 1, mb_y as isize); let b = at(mb_x as isize, mb_y as isize - 1); let c = at(mb_x as isize + 1, mb_y as isize - 1) .or_else(|| at(mb_x as isize - 1, mb_y as isize - 1)); let sad = |n: Option<(bool, u32)>| n.filter(|&(s, _)| s).map_or(0, |(_, v)| v);
let (sa, sb, sc) = (sad(a), sad(b), sad(c));
if b.is_none() && c.is_none() && a.is_some() {
return sa;
}
match (
a.is_some_and(|(s, _)| s),
b.is_some_and(|(s, _)| s),
c.is_some_and(|(s, _)| s),
) {
(true, false, false) => sa,
(false, true, false) => sb,
(false, false, true) => sc,
_ => sb.max(sa.min(sc)).min(sa.max(sc)), }
}
fn qp_delta(&mut self) -> i32 {
let d = self.qp as i32 - self.cur_qp as i32;
self.cur_qp = self.qp;
d
}
fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
let w4 = self.mb_w * 4;
let get = |avail: bool, bx: isize, by: isize| {
if avail {
let idx = by as usize * w4 + bx as usize;
MvNeighbor {
available: true,
mv: self.mv_y[idx],
ref_idx: self.ref_idx_y[idx],
}
} else {
MvNeighbor::NONE
}
};
let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
let a = get(mb_x > 0, bx - 1, by);
let b = get(mb_y > 0, bx, by - 1);
let c = if mb_y > 0 && mb_x + 1 < self.mb_w {
get(true, bx + 4, by - 1)
} else {
get(mb_x > 0 && mb_y > 0, bx - 1, by - 1)
};
[a, b, c]
}
fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
if !a.available
|| !b.available
|| (a.ref_idx == 0 && a.mv == (0, 0))
|| (b.ref_idx == 0 && b.mv == (0, 0))
{
(0, 0)
} else {
predict_mv(a, b, c, 0)
}
}
fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
let w4 = self.mb_w * 4;
for dy in 0..4 {
for dx in 0..4 {
let idx = (mb_y * 4 + dy) * w4 + (mb_x * 4 + dx);
self.mv_y[idx] = mv;
self.inter_y[idx] = inter;
self.ref_idx_y[idx] = if inter { refi } else { -1 };
}
}
}
fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
let get = |bx: isize, by: isize| -> MvNeighbor {
if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
MvNeighbor::NONE
} else {
let idx = (by * w4 + bx) as usize;
MvNeighbor { available: true, mv: self.mv_y[idx], ref_idx: self.ref_idx_y[idx] }
}
};
let a = get(pbx - 1, pby);
let b = get(pbx, pby - 1);
let mut c = get(pbx + pwb, pby - 1);
if !c.available {
c = get(pbx - 1, pby - 1); }
[a, b, c]
}
fn mv_neighbors_block_list(&self, pbx: isize, pby: isize, pwb: isize, list: usize) -> [MvNeighbor; 3] {
let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
let (mvg, refg): (&[(i32, i32)], &[i32]) = if list == 0 {
(&self.mv_y, &self.ref_idx_y)
} else {
(&self.mv1_y, &self.ref_idx1_y)
};
let get = |bx: isize, by: isize| -> MvNeighbor {
if bx < 0 || by < 0 || bx >= w4 || by >= h4 || !self.coded_y[(by * w4 + bx) as usize] {
MvNeighbor::NONE
} else {
let idx = (by * w4 + bx) as usize;
MvNeighbor { available: true, mv: mvg[idx], ref_idx: refg[idx] }
}
};
let a = get(pbx - 1, pby);
let b = get(pbx, pby - 1);
let mut c = get(pbx + pwb, pby - 1);
if !c.available {
c = get(pbx - 1, pby - 1); }
[a, b, c]
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn mc_satd_hp(
&self,
reference: &crate::RefFrame,
hp: Option<&rusty_h264_common::inter::HpelPlanes>,
hr_on: bool,
sa_on: bool,
src_row: &[u8],
lx: usize,
ly: usize,
rw: usize,
rh: usize,
mv: (i32, i32),
) -> i64 {
#[cfg(not(accel))]
let _ = sa_on;
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
let ch = self.mb_h * 16;
let cw = self.cw;
let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
let interior_fullpel = mv.0 & 3 == 0
&& mv.1 & 3 == 0
&& ix0 >= 0
&& iy0 >= 0
&& ix0 + rw as isize <= cw as isize
&& iy0 + rh as isize <= ch as isize;
#[cfg(feature = "profile")]
{
let fullpel = mv.0 & 3 == 0 && mv.1 & 3 == 0;
satdpath::bump(if interior_fullpel { 0 } else if fullpel { 1 } else { 2 });
}
if interior_fullpel {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
let (rx0, ry0) = (ix0 as usize, iy0 as usize);
return satd_px(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
}
if let Some(hp) = hp {
if hr_on {
if let Some((plane, base, stride)) =
rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
{
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
return satd_px(src_row, cw, &plane[base..], stride, rw, rh);
}
}
#[cfg(accel)]
if sa_on {
if let Some((pa, ba, pb, bb, stride)) =
rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
{
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeCost);
if let Some(v) = rusty_h264_accel::satd_avg(
src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh,
) {
return v as i64;
}
}
}
let mut pred = [0u8; 256];
if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
return satd_px(src_row, cw, &pred, rw, rw, rh);
}
mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
return satd_px(src_row, cw, &pred, rw, rw, rh);
}
let mut pred = [0u8; 256];
mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
satd_px(src_row, cw, &pred, rw, rw, rh)
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn mc_sad_hp(
&self,
reference: &crate::RefFrame,
hp: Option<&rusty_h264_common::inter::HpelPlanes>,
hr_on: bool,
src_row: &[u8],
lx: usize,
ly: usize,
rw: usize,
rh: usize,
mv: (i32, i32),
_asrc: Option<&[u8; 256]>,
) -> i64 {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
let ch = self.mb_h * 16;
let cw = self.cw;
let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
let interior_fullpel = mv.0 & 3 == 0
&& mv.1 & 3 == 0
&& ix0 >= 0
&& iy0 >= 0
&& ix0 + rw as isize <= cw as isize
&& iy0 + rh as isize <= ch as isize;
if interior_fullpel {
let (rx0, ry0) = (ix0 as usize, iy0 as usize);
#[cfg(accel)]
if rw == 16 && rh == 16 {
if let Some(src) = _asrc {
return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
as i64;
}
}
return sad_strided(src_row, cw, &reference.y[ry0 * cw + rx0..], cw, rw, rh);
}
if let Some(hp) = hp {
if hr_on {
if let Some((plane, base, stride)) =
rusty_h264_common::inter::hpel_ref(hp, lx, ly, rw, rh, mv.0, mv.1)
{
return sad_strided(src_row, cw, &plane[base..], stride, rw, rh);
}
if let Some((pa, ba, pb, bb, stride)) =
rusty_h264_common::inter::hpel_qpel_refs(hp, lx, ly, rw, rh, mv.0, mv.1)
{
return sad_avg_strided(src_row, cw, &pa[ba..], &pb[bb..], stride, rw, rh);
}
}
let mut pred = [0u8; 256];
if rusty_h264_common::inter::hpel_block(hp, lx, ly, rw, rh, mv.0, mv.1, &mut pred) {
return sad_strided(src_row, cw, &pred, rw, rw, rh);
}
mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
return sad_strided(src_row, cw, &pred, rw, rw, rh);
}
let mut pred = [0u8; 256];
mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
sad_strided(src_row, cw, &pred, rw, rw, rh)
}
#[allow(clippy::too_many_arguments)]
fn mc_sad(
&self,
reference: &crate::RefFrame,
sy: &[u8],
lx: usize,
ly: usize,
rw: usize,
rh: usize,
mv: (i32, i32),
_asrc: Option<&[u8; 256]>,
) -> i64 {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(2);
let ch = self.mb_h * 16;
let cw = self.cw;
let (ix0, iy0) = (lx as isize + (mv.0 >> 2) as isize, ly as isize + (mv.1 >> 2) as isize);
let interior_fullpel = mv.0 & 3 == 0
&& mv.1 & 3 == 0
&& ix0 >= 0
&& iy0 >= 0
&& ix0 + rw as isize <= cw as isize
&& iy0 + rh as isize <= ch as isize;
#[cfg(accel)]
if interior_fullpel && rw == 16 && rh == 16 {
if let Some(src) = _asrc {
let (rx0, ry0) = (ix0 as usize, iy0 as usize);
return rusty_h264_accel::sad_16x16(src, 16, &reference.y[ry0 * cw + rx0..], cw)
as i64;
}
}
let mut sad = 0u32;
if interior_fullpel {
let (rx0, ry0) = (ix0 as usize, iy0 as usize);
let refy = &reference.y;
for dy in 0..rh {
let s = &sy[(ly + dy) * cw + lx..][..rw];
let r = &refy[(ry0 + dy) * cw + rx0..][..rw];
sad += s.iter().zip(r).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
} else {
let mut pred = [0u8; 256];
let from_planes = !self.fast
&& rusty_h264_common::inter::hpel_block(
reference.hpel(cw, ch),
lx,
ly,
rw,
rh,
mv.0,
mv.1,
&mut pred,
);
if !from_planes {
mc_luma(&reference.y, cw, ch, lx, ly, rw, rh, mv.0, mv.1, &mut pred);
}
for dy in 0..rh {
let s = &sy[(ly + dy) * cw + lx..][..rw];
let p = &pred[dy * rw..][..rw];
sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
}
sad as i64
}
fn bi_dist(
&self,
l0: &crate::RefFrame,
l1: &crate::RefFrame,
sy: &[u8],
lx: usize,
ly: usize,
mv0: (i32, i32),
mv1: (i32, i32),
) -> i64 {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
let ch = self.mb_h * 16;
let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
mc_luma(&l0.y, self.cw, ch, lx, ly, 16, 16, mv0.0, mv0.1, &mut a);
mc_luma(&l1.y, self.cw, ch, lx, ly, 16, 16, mv1.0, mv1.1, &mut b);
let mut avg = [0u8; 256];
for i in 0..256 {
avg[i] = bi_blend(a[i] as i32, b[i] as i32, self.bi_w);
}
if self.fast && !self.mb_use_satd {
let mut sad = 0u32;
for dy in 0..16 {
let s = &sy[(ly + dy) * self.cw + lx..][..16];
let p = &avg[dy * 16..][..16];
sad += s.iter().zip(p).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
}
sad as i64
} else {
satd_px(&sy[ly * self.cw + lx..], self.cw, &avg, 16, 16, 16)
}
}
fn pred_dist(&self, sy: &[u8], lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
if self.fast && !self.mb_use_satd {
let mut sad = 0u32;
for dy in 0..16 {
let s = &sy[(ly + dy) * self.cw + lx..][..16];
let p = &pred[dy * 16..][..16];
sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
sad as i64
} else {
satd_px(&sy[ly * self.cw + lx..], self.cw, pred, 16, 16, 16)
}
}
fn col_zero(&self, l1: &crate::RefFrame, bx: usize, by: usize) -> bool {
if l1.w4 == 0 {
return false;
}
let idx = by * l1.w4 + bx;
if idx >= l1.ref_idx.len() {
return false;
}
l1.ref_idx[idx] == 0 && l1.mv[idx].0.abs() <= 1 && l1.mv[idx].1.abs() <= 1
}
#[allow(clippy::too_many_arguments)]
fn b_mc_block(
&self,
l0: &crate::RefFrame,
l1: &crate::RefFrame,
mb_x: usize,
mb_y: usize,
dx: usize,
dy: usize,
refi0: i32,
m0: (i32, i32),
refi1: i32,
m1: (i32, i32),
pred_y: &mut [u8; 256],
c_pred: &mut [[u8; 64]; 2],
) {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(4);
let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
let (px, py) = (mb_x * 16 + dx, mb_y * 16 + dy);
let (mut a, mut b) = ([0u8; 16], [0u8; 16]);
let mc4 = |r: &crate::RefFrame, mv: (i32, i32), out: &mut [u8; 16]| {
if !self.fast
&& bdirect_planes_enabled()
&& rusty_h264_common::inter::hpel_block(
r.hpel(self.cw, ch), px, py, 4, 4, mv.0, mv.1, out,
)
{
return;
}
mc_luma(&r.y, self.cw, ch, px, py, 4, 4, mv.0, mv.1, out);
};
if refi0 >= 0 {
mc4(l0, m0, &mut a);
}
if refi1 >= 0 {
mc4(l1, m1, &mut b);
}
for yy in 0..4 {
for xx in 0..4 {
let i = yy * 4 + xx;
let v = match (refi0 >= 0, refi1 >= 0) {
(true, true) => bi_blend(a[i] as i32, b[i] as i32, self.bi_w),
(true, false) => a[i],
_ => b[i],
};
pred_y[(dy + yy) * 16 + (dx + xx)] = v;
}
}
let (cpx, cpy) = (mb_x * 8 + dx / 2, mb_y * 8 + dy / 2);
for c in 0..2 {
let (r0, r1) = if c == 0 { (&l0.u, &l1.u) } else { (&l0.v, &l1.v) };
let (mut ca, mut cb) = ([0u8; 4], [0u8; 4]);
if refi0 >= 0 {
mc_chroma(r0, self.ccw, cch, cpx, cpy, 2, 2, m0.0, m0.1, &mut ca);
}
if refi1 >= 0 {
mc_chroma(r1, self.ccw, cch, cpx, cpy, 2, 2, m1.0, m1.1, &mut cb);
}
for yy in 0..2 {
for xx in 0..2 {
let i = yy * 2 + xx;
let v = match (refi0 >= 0, refi1 >= 0) {
(true, true) => bi_blend(ca[i] as i32, cb[i] as i32, self.bi_w),
(true, false) => ca[i],
_ => cb[i],
};
c_pred[c][(dy / 2 + yy) * 8 + (dx / 2 + xx)] = v;
}
}
}
}
fn b_direct(
&self,
l0: &crate::RefFrame,
l1: &crate::RefFrame,
mb_x: usize,
mb_y: usize,
) -> ([u8; 256], [[u8; 64]; 2], [(i32, (i32, i32), i32, (i32, i32)); 16]) {
let (nbx, nby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
let n0 = self.mv_neighbors_block_list(nbx, nby, 4, 0);
let n1 = self.mv_neighbors_block_list(nbx, nby, 4, 1);
let min_pos = |a: i32, b: i32| if a < 0 { b } else if b < 0 { a } else { a.min(b) };
let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
let (mut refi0, mut refi1) = (rid(&n0), rid(&n1));
let direct_zero = refi0 < 0 && refi1 < 0;
if direct_zero {
refi0 = 0;
refi1 = 0;
}
let mv0 = if refi0 >= 0 && !direct_zero { predict_mv(n0[0], n0[1], n0[2], refi0) } else { (0, 0) };
let mv1 = if refi1 >= 0 && !direct_zero { predict_mv(n1[0], n1[1], n1[2], refi1) } else { (0, 0) };
let mut pred_y = [0u8; 256];
let mut c_pred = [[0u8; 64]; 2];
let mut motion = [(0i32, (0i32, 0i32), 0i32, (0i32, 0i32)); 16];
for sby in 0..4 {
for sbx in 0..4 {
let cz = !direct_zero && self.col_zero(l1, mb_x * 4 + sbx, mb_y * 4 + sby);
let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
motion[sby * 4 + sbx] = (refi0, m0, refi1, m1);
self.b_mc_block(l0, l1, mb_x, mb_y, sbx * 4, sby * 4, refi0, m0, refi1, m1, &mut pred_y, &mut c_pred);
}
}
(pred_y, c_pred, motion)
}
fn commit_direct_motion(&mut self, mb_x: usize, mb_y: usize, motion: &[(i32, (i32, i32), i32, (i32, i32)); 16]) {
let w4 = self.mb_w * 4;
for sby in 0..4 {
for sbx in 0..4 {
let (refi0, m0, refi1, m1) = motion[sby * 4 + sbx];
let idx = (mb_y * 4 + sby) * w4 + (mb_x * 4 + sbx);
self.inter_y[idx] = true;
self.coded_y[idx] = true;
self.mv_y[idx] = m0;
self.ref_idx_y[idx] = refi0;
self.mv1_y[idx] = m1;
self.ref_idx1_y[idx] = refi1;
}
}
}
#[allow(clippy::too_many_arguments)]
fn motion_search(
&self,
reference: &crate::RefFrame,
sy: &[u8],
lx: usize,
ly: usize,
rw: usize,
rh: usize,
predictors: &[(i32, i32)],
lambda_me: f64,
start: Option<(i32, i32)>,
) -> ((i32, i32), i64) {
let mvk = mv_cost_kind(self.mv_smooth);
let mvbits = |d: i32| -> u32 {
match mvk {
1 => {
let a = d.unsigned_abs().min(4095) as usize;
MV_COST_TAB.get_or_init(build_mv_cost)[a] as u32
}
2 => {
let a = d.unsigned_abs().min(4095) as usize;
MV_TRUE_BIASED.get_or_init(build_true_biased)[a] as u32
}
_ => {
let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
1 + 2 * (31 - (codenum + 1).leading_zeros())
}
}
};
let center = predictors[0];
let probe = me_oracle_on();
let sadfp = !self.fast && start.is_none() && self.sadfp;
#[cfg(accel)]
let asrc_buf = if (self.fast || sadfp) && rw == 16 && rh == 16 {
let mut a = AlignedMb([0u8; 256]);
for dy in 0..16 {
a.0[dy * 16..dy * 16 + 16].copy_from_slice(&sy[(ly + dy) * self.cw + lx..][..16]);
}
Some(a)
} else {
None
};
#[cfg(accel)]
let asrc: Option<&[u8; 256]> = asrc_buf.as_ref().map(|a| &a.0);
#[cfg(not(accel))]
let asrc: Option<&[u8; 256]> = None;
let use_sad = self.fast && !self.mb_use_satd;
let cw = self.cw;
let hp: Option<&rusty_h264_common::inter::HpelPlanes> =
if !self.fast { Some(reference.hpel(cw, self.mb_h * 16)) } else { None };
let hr_on = hpel_ref_enabled();
let sa_on = cfg!(accel) && hr_on && satd_avg_enabled();
let src_row = &sy[ly * cw + lx..];
#[cfg(accel)]
let mectx = if !use_sad && mectx_enabled() {
hp.and_then(|p| {
rusty_h264_accel::MeCtx::new(
src_row, cw, &p.f, &p.h, &p.v, &p.c, p.stride, p.pad, p.pw, p.ph,
lx, ly, rw, rh,
)
})
} else {
None
};
let cost = |mv: (i32, i32)| -> i64 {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
let lam_r = if mvk != 0 { lambda_me * 0.25 } else { lambda_me };
let dist = if use_sad {
self.mc_sad(reference, sy, lx, ly, rw, rh, mv, asrc)
} else {
#[cfg(accel)]
{
match mectx.as_ref().and_then(|c| c.eval(mv.0, mv.1)) {
Some(d) => d as i64,
None => {
self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
}
}
}
#[cfg(not(accel))]
{
self.mc_satd_hp(reference, hp, hr_on, sa_on, src_row, lx, ly, rw, rh, mv)
}
};
dist + (lam_r * rate as f64) as i64
};
let lam_fp = lambda_me * if sadfp { me_sadfp_lambda() } else { 1.0 };
let cost_fp = |mv: (i32, i32)| -> i64 {
if !sadfp {
return cost(mv);
}
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
self.mc_sad_hp(reference, hp, hr_on, src_row, lx, ly, rw, rh, mv, asrc)
+ (lam_fp * rate as f64) as i64
};
let refine_only = start.is_some();
let (mut best, mut best_c) = match start {
Some(mv) => (mv, cost(mv)),
None => {
let mut b = (0, 0);
let mut bc = cost_fp(b);
for &p in predictors {
let pc = cost_fp(p);
if pc < bc {
bc = pc;
b = p;
}
}
(b, bc)
}
};
let (seed_mv, mut seed_c) = (best, best_c);
if !refine_only && self.me_snap && (best.0 & 3 != 0 || best.1 & 3 != 0) {
let snapped = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
best_c = cost_fp(snapped);
best = snapped;
}
let mut ladder = [0i32; 5];
let mut nladder = 0usize;
let steps: &[i32] = if self.fast {
&[16, 4]
} else {
let m = dia_mask();
for (i, r) in DIA_RUNGS.iter().enumerate() {
if m & (1 << i) != 0 {
ladder[nladder] = *r;
nladder += 1;
}
}
&ladder[..nladder]
};
let _gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeDiamond);
let fc = !self.fast && cfg!(accel) && me_fc_enabled()
&& matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
let ch_px = self.mb_h as isize * 16;
for (_si, &step) in steps.iter().enumerate() {
if refine_only {
break;
}
loop {
#[cfg(accel)]
if fc && best.0 & 3 == 0 && best.1 & 3 == 0 {
let s = (step >> 2) as isize;
let (bx, by) = (lx as isize + (best.0 >> 2) as isize, ly as isize + (best.1 >> 2) as isize);
if bx - s >= 0 && by - s >= 0 && bx + s + rw as isize <= cw as isize && by + s + rh as isize <= ch_px {
let offs = [
(by * cw as isize + bx + s) as usize,
(by * cw as isize + bx - s) as usize,
((by + s) * cw as isize + bx) as usize,
((by - s) * cw as isize + bx) as usize,
];
let batch = if rw != 16 {
None
} else if sadfp {
rusty_h264_accel::sad_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
} else {
rusty_h264_accel::satd_x4(src_row, cw, &reference.y, offs, cw, rw, rh)
};
{
let ring = [(step, 0), (-step, 0), (0, step), (0, -step)];
let (mut bi, mut bc) = (usize::MAX, best_c);
for (i, &(dx, dy)) in ring.iter().enumerate() {
let mv = (best.0 + dx, best.1 + dy);
let cc = match batch {
Some(sads) => {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
sads[i] as i64 + (lam_fp * rate as f64) as i64
}
None => cost_fp(mv),
};
#[cfg(feature = "profile")]
diastats::ev(_si);
if cc < bc {
bc = cc;
bi = i;
}
}
if bi == usize::MAX {
break;
}
best_c = bc;
best = (best.0 + ring[bi].0, best.1 + ring[bi].1);
#[cfg(feature = "profile")]
diastats::imp(_si);
continue;
}
}
}
let mut improved = false;
for &(dx, dy) in &[(step, 0), (-step, 0), (0, step), (0, -step)] {
let c = (best.0 + dx, best.1 + dy);
let cc = cost_fp(c);
#[cfg(feature = "profile")]
diastats::ev(_si);
if cc < best_c {
best_c = cc;
best = c;
improved = true;
#[cfg(feature = "profile")]
diastats::imp(_si);
}
}
if !improved {
break;
}
}
}
drop(_gd);
if sadfp {
best_c = cost(best);
seed_c = cost(seed_mv);
}
let _gr = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeRescue);
let flat = |sself: &Self| {
!refine_only && {
let (mut s, mut ss) = (0u64, 0u64);
for dy in 0..rh {
for dx in 0..rw {
let v = sy[(ly + dy) * sself.cw + lx + dx] as u64;
s += v;
ss += v * v;
}
}
let n = (rw * rh) as u64;
(ss - s * s / n) / n < sself.me_wide_var
}
};
if self.me_wide && !self.fast && (self.me_fast || flat(self)) && !self.resc_off.get() {
let rate_b = mvbits(best.0 - center.0) + mvbits(best.1 - center.1);
let dist = best_c - (lambda_me * rate_b as f64) as i64;
if dist / (rw * rh).max(1) as i64 > self.me_rescue {
let pre_c = best_c;
let (cx, cy) = ((best.0 + 2).div_euclid(4) * 4, (best.1 + 2).div_euclid(4) * 4);
let mut gb = best;
let cw = self.cw;
let r = self.me_range;
let batched = rw == 16 && rh == 16 && cfg!(accel) && {
let (icdx, icdy) = (cx >> 2, cy >> 2);
lx as i32 + icdx >= r
&& lx as i32 + icdx + r + 16 <= cw as i32
&& ly as i32 + icdy >= r
&& ly as i32 + icdy + r + 16 <= (self.mb_h * 16) as i32
&& me_batch_enabled()
};
#[cfg(accel)]
if batched {
let (icdx, icdy) = ((cx >> 2), (cy >> 2));
let src = &sy[ly * cw + lx..];
let mut dy = -r;
while dy <= r {
let rby = (ly as i32 + icdy + dy) as usize;
let mut dx = -r;
while dx <= r {
let rbx = (lx as i32 + icdx + dx) as usize;
let satd =
2 * rusty_h264_accel::satd_16x16(src, cw, &reference.y[rby * cw + rbx..], cw) as i64;
let mv = (cx + dx * 4, cy + dy * 4);
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
let cc = satd + (lambda_me * rate as f64) as i64;
if cc < best_c {
best_c = cc;
gb = mv;
}
dx += 2;
}
dy += 2;
}
}
if !batched {
let mut dy = -r;
while dy <= r {
let mut dx = -r;
while dx <= r {
let cc = cost((cx + dx * 4, cy + dy * 4));
if cc < best_c {
best_c = cc;
gb = (cx + dx * 4, cy + dy * 4);
}
dx += 2;
}
dy += 2;
}
}
best = gb;
for dy in -1..=1 {
for dx in -1..=1 {
let c = (best.0 + dx * 4, best.1 + dy * 4);
let cc = cost(c);
if cc < best_c {
best_c = cc;
best = c;
}
}
}
let n = self.resc_n.get();
if n < self.me_learn {
self.resc_n.set(n + 1);
if best_c * 16 <= pre_c * 15 {
self.resc_big.set(self.resc_big.get() + 1);
}
if n + 1 == self.me_learn
&& self.resc_big.get() * 100 < self.me_learn * self.me_payoff_pct
{
self.resc_off.set(true);
}
}
}
}
drop(_gr);
let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MeSubpel);
if probe {
let mut ob = center;
let mut oc = i64::MAX;
for gy in -24i32..=24 {
for gx in -24i32..=24 {
let c = (center.0 + gx * 4, center.1 + gy * 4);
let cc = cost(c);
if cc < oc {
oc = cc;
ob = c;
}
}
}
let fullpel_best = ob;
for &st in &[2i32, 1] {
for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
let c = (ob.0 + dx, ob.1 + dy);
let cc = cost(c);
if cc < oc {
oc = cc;
ob = c;
}
}
}
let mut oc_sp = oc;
for dy in -3i32..=3 {
for dx in -3i32..=3 {
let c = (fullpel_best.0 + dx, fullpel_best.1 + dy);
let cc = cost(c);
if cc < oc_sp {
oc_sp = cc;
}
}
}
let (mut mb_, mut mc_) = (best, best_c);
for &st in &[2i32, 1] {
for &(dx, dy) in &[(st, 0), (-st, 0), (0, st), (0, -st)] {
let c = (mb_.0 + dx, mb_.1 + dy);
let cc = cost(c);
if cc < mc_ {
mc_ = cc;
mb_ = c;
}
}
}
use std::sync::atomic::Ordering::Relaxed;
ME_PROBE[0].fetch_add(1, Relaxed);
ME_PROBE[1].fetch_add(mc_.max(0) as u64, Relaxed);
ME_PROBE[2].fetch_add(oc.max(0) as u64, Relaxed);
ME_PROBE[3].fetch_add((mc_ > oc) as u64, Relaxed);
ME_PROBE[5].fetch_add(oc_sp.max(0) as u64, Relaxed);
ME_PROBE[6].fetch_add((mc_ > oc_sp) as u64, Relaxed);
}
let subpel: &[i32] = if (self.fast && !self.subpel_force) || (self.sp_defer.get() && !refine_only) {
&[]
} else {
&[2, 1]
};
let (hv_pre, mut hv_evals) = (best_c, 0u32);
let (mut hv_to_best, mut hv_ring1) = (0u32, i64::MIN);
let mut pat = subpel_pattern_override()
.unwrap_or(if self.sp_single_pass { 2 } else { 0 });
let (sp_learn, sp_t) = sp_dispatch_cfg();
let sp_dispatching = sp_learn > 0 && pat == 0 && !subpel.is_empty();
if sp_dispatching && self.sp_learn_n.get() >= sp_learn && self.sp_1pass.get() {
pat = 2;
}
const SP_MEMO_N: usize = 64;
#[inline(always)]
fn sp_slot(mv: (i32, i32)) -> usize {
((mv.0 & 7) as usize) | (((mv.1 & 7) as usize) << 3)
}
let mut memo_mv = [(i32::MIN, i32::MIN); SP_MEMO_N];
let mut memo_c = [0i64; SP_MEMO_N];
if !subpel.is_empty() {
let s0 = sp_slot(best);
memo_mv[s0] = best;
memo_c[s0] = best_c;
}
#[cfg(feature = "profile")]
let mut seen: Vec<(i32, i32)> = Vec::with_capacity(64);
#[cfg(feature = "profile")]
{
seen.push(best);
}
let sp_cap = sp_maxit();
let sp_fc = sp_fc_enabled() && !self.fast && cfg!(accel)
&& matches!((rw, rh), (16, 16) | (16, 8) | (8, 16) | (8, 8));
for &step in subpel {
let ring8 = [
(step, 0), (-step, 0), (0, step), (0, -step),
(step, step), (-step, -step), (step, -step), (-step, step),
];
let ring4 = [(step, 0), (-step, 0), (0, step), (0, -step)];
let ring: &[(i32, i32)] = if pat & 1 != 0 { &ring4 } else { &ring8 };
let mut _iter = 0u32;
loop {
#[cfg(accel)]
if sp_fc && step == 1 && pat & 1 == 0 {
_iter += 1;
let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
let ring8 = [
(1, 0), (-1, 0), (0, 1), (0, -1),
(1, 1), (-1, -1), (1, -1), (-1, 1),
];
let mut prs: [Option<(&[u8], usize, &[u8], usize, usize)>; 8] = [None; 8];
let mut all = true;
for (i, &(dx, dy)) in ring8.iter().enumerate() {
prs[i] = rusty_h264_common::inter::hpel_qpel_refs(
hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
);
all &= prs[i].is_some();
}
if all {
let stride = prs[0].unwrap().4;
let pack = |a: usize, b: usize, c2: usize, d: usize| {
if rw != 16 {
return None;
}
let g = |i: usize| {
let (pa, oa, pb, ob, _) = prs[i].unwrap();
(pa, oa, pb, ob)
};
rusty_h264_accel::satd_avg_x4(
src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
)
};
{
let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
let (mut bi, mut bc) = (usize::MAX, best_c);
for i in 0..8 {
let (dx, dy) = ring8[i];
let mv = (best.0 + dx, best.1 + dy);
let cc = match (i < 4, &ax, &di) {
(true, Some(ax), _) => {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
ax[i] as i64 + (lambda_me * rate as f64) as i64
}
(false, _, Some(di)) => {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
di[i - 4] as i64 + (lambda_me * rate as f64) as i64
}
_ => cost(mv),
};
hv_evals += 1;
if cc < bc {
bc = cc;
bi = i;
}
}
if hv_ring1 == i64::MIN {
hv_ring1 = if bi == usize::MAX { best_c } else { bc };
}
if bi == usize::MAX
|| !self.me_subpel_iter
|| pat & 2 != 0
|| (sp_cap != 0 && _iter >= sp_cap)
{
if bi != usize::MAX {
best_c = bc;
best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
hv_to_best = hv_evals;
}
break;
}
best_c = bc;
best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
hv_to_best = hv_evals;
continue;
}
}
_iter -= 1;
}
#[cfg(accel)]
if sp_fc && step == 2 && best.0 & 3 == 0 && best.1 & 3 == 0 && pat & 1 == 0 {
_iter += 1;
let hp8 = hp.expect("sp_fc implies non-fast, which resolves hp");
let ring8 = [
(step, 0), (-step, 0), (0, step), (0, -step),
(step, step), (-step, -step), (step, -step), (-step, step),
];
let mut refs8: [Option<(&[u8], usize, usize)>; 8] = [None; 8];
let mut all = true;
for (i, &(dx, dy)) in ring8.iter().enumerate() {
refs8[i] = rusty_h264_common::inter::hpel_ref(
hp8, lx, ly, rw, rh, best.0 + dx, best.1 + dy,
);
all &= refs8[i].is_some();
}
if all {
let stride = refs8[0].unwrap().2;
let pack = |a: usize, b: usize, c2: usize, d: usize| {
if rw != 16 {
return None;
}
let g = |i: usize| {
let (p, o, _) = refs8[i].unwrap();
(p, o)
};
rusty_h264_accel::satd_x4p(
src_row, cw, [g(a), g(b), g(c2), g(d)], stride, rw, rh,
)
};
{
let (ax, di) = (pack(0, 1, 2, 3), pack(4, 5, 6, 7));
let (mut bi, mut bc) = (usize::MAX, best_c);
for i in 0..8 {
let (dx, dy) = ring8[i];
let mv = (best.0 + dx, best.1 + dy);
let cc = match (i < 4, &ax, &di) {
(true, Some(ax), _) => {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
ax[i] as i64 + (lambda_me * rate as f64) as i64
}
(false, _, Some(di)) => {
let rate = mvbits(mv.0 - center.0) + mvbits(mv.1 - center.1);
di[i - 4] as i64 + (lambda_me * rate as f64) as i64
}
_ => cost(mv),
};
hv_evals += 1;
if cc < bc {
bc = cc;
bi = i;
}
}
if hv_ring1 == i64::MIN {
hv_ring1 = if bi == usize::MAX { best_c } else { bc };
}
if bi == usize::MAX
|| !self.me_subpel_iter
|| pat & 2 != 0
|| (sp_cap != 0 && _iter >= sp_cap)
{
if bi != usize::MAX {
best_c = bc;
best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
hv_to_best = hv_evals;
}
break;
}
best_c = bc;
best = (best.0 + ring8[bi].0, best.1 + ring8[bi].1);
hv_to_best = hv_evals;
continue;
}
}
_iter -= 1; }
let mut improved = false;
_iter += 1;
for (_pi, &(dx, dy)) in ring.iter().enumerate() {
let c = (best.0 + dx, best.1 + dy);
let slot = sp_slot(c);
let cc = if memo_mv[slot] == c {
memo_c[slot]
} else {
let v = cost(c);
memo_mv[slot] = c;
memo_c[slot] = v;
v
};
hv_evals += 1;
#[cfg(feature = "profile")]
{
spstats::ev(if step == 2 { 0 } else { 1 }, _pi, _iter);
if seen.contains(&c) {
spstats::redundant();
} else {
seen.push(c);
}
}
if cc < best_c {
best_c = cc;
best = c;
improved = true;
hv_to_best = hv_evals;
#[cfg(feature = "profile")]
spstats::imp(if step == 2 { 0 } else { 1 }, _pi, _iter);
}
}
if hv_ring1 == i64::MIN {
hv_ring1 = best_c;
}
if !improved
|| !self.me_subpel_iter
|| pat & 2 != 0
|| (sp_cap != 0 && _iter >= sp_cap)
{
break;
}
}
}
if sp_dispatching {
let n = self.sp_learn_n.get();
if n < sp_learn {
self.sp_learn_n.set(n + 1);
if hv_ring1 != i64::MIN {
self.sp_ring1.set(self.sp_ring1.get() + (hv_pre - hv_ring1).max(0));
self.sp_total.set(self.sp_total.get() + (hv_pre - best_c).max(0));
}
if n + 1 == sp_learn {
let tot = self.sp_total.get();
self.sp_1pass.set(tot > 0 && self.sp_ring1.get() * 100 >= tot * sp_t);
}
}
}
if !subpel.is_empty() && subpel_harvest::enabled() {
subpel_harvest::record(hv_pre, best_c, lambda_me, rw, rh, hv_evals, hv_to_best, hv_ring1);
}
if self.me_snap && seed_c < best_c {
best = seed_mv;
best_c = seed_c;
}
(best, best_c)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn encode_inter_mb(
&mut self,
w: &mut BitWriter,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
) {
if self.coded_path_v2 {
self.encode_inter_mb_v2(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
} else {
self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
}
}
#[allow(clippy::too_many_arguments)]
fn encode_inter_mb_v2(
&mut self,
w: &mut BitWriter,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
) {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
#[cfg(not(accel))]
{
self.encode_inter_mb_v1(w, refs, sy, su, sv, mb_x, mb_y, mode, parts);
}
#[cfg(accel)]
{
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
let (qp, qpc) = (self.qp, self.qpc);
let w4 = self.mb_w * 4;
let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
let mut pred_y = [0u8; 256];
let mut c_pred = [[0u8; 64]; 2];
let mut mvds = [(0i32, 0i32); 4];
let mut n_mvd = 0;
let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
let (refi, mv) = parts[part];
let reference = &refs[refi as usize];
let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
let pmv = predict_partition_mv(mode, part, a, b, c, refi);
mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
n_mvd += 1;
for by in ry / 4..ry / 4 + rh / 4 {
for bx in rx / 4..rx / 4 + rw / 4 {
let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
self.mv_y[idx] = mv;
self.inter_y[idx] = true;
self.ref_idx_y[idx] = refi;
self.coded_y[idx] = true;
}
}
if rw == 16 && rh == 16 {
self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
} else {
let mut tmp = [0u8; 256];
self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
if rw == 8 {
for dy in 0..rh {
pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
}
} else {
for dy in 0..rh {
pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
}
}
}
let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
for cc in 0..2 {
let rc = if cc == 0 { &reference.u } else { &reference.v };
if crw == 8 && crh == 8 {
mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
} else {
let mut tc = [0u8; 64];
mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
if crw == 4 {
for dy in 0..crh {
c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
}
} else {
for dy in 0..crh {
c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
}
}
}
}
}
let mut dctw = AlignedDct([0i16; 256]);
let dct = &mut dctw.0;
let mut cbp_luma = 0u32;
drop(_g_mc);
let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
let base = mb_y * 16 * self.cw + mb_x * 16;
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
rusty_h264_accel::dct_four_t4(
&mut dct[qi * 64..qi * 64 + 64],
&sy[base + qy * self.cw + qx..],
self.cw,
&pred_y[qy * 16 + qx..],
16,
);
}
let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
for qi in 0..4 {
rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
}
for blk in 0..16 {
if dct[blk * 16..blk * 16 + 16].iter().any(|&v| v != 0) {
cbp_luma |= 1 << (blk / 4);
}
}
let mut c_dc_levels = [[0i32; 4]; 2];
let mut c_recon_dc = [[0i32; 4]; 2];
let mut c_q = [[[0i32; 16]; 4]; 2];
let (mut any_ac, mut any_dc) = (false, false);
for c in 0..2 {
let src = if c == 0 { su } else { sv };
let dc2x2 = {
#[repr(align(16))]
struct A([i16; 64]);
let mut cdct = A([0i16; 64]);
rusty_h264_accel::dct_four_t4(
&mut cdct.0,
&src[(mb_y * 8) * self.ccw + mb_x * 8..],
self.ccw,
&c_pred[c],
8,
);
let dc = [cdct.0[0] as i32, cdct.0[16] as i32, cdct.0[32] as i32, cdct.0[48] as i32];
let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
rusty_h264_accel::quant_four_4x4(&mut cdct.0, &ffc, mfc);
for i in 0..4 {
let q = &mut c_q[c][i];
q[0] = 0;
for j in 1..16 {
let v = cdct.0[i * 16 + j] as i32;
q[j] = v;
if v != 0 {
any_ac = true;
}
}
}
dc
};
let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
if dl.iter().any(|&v| v != 0) {
any_dc = true;
}
c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
c_dc_levels[c] = dl;
}
let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
let cbp = cbp_luma | (cbp_chroma << 4);
drop(_g_tq);
let _g_syn = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
w.write_ue(mode as u32);
let num_refs = refs.len();
if num_refs > 1 {
for &(refi, _) in parts {
write_ref_idx(w, refi, num_refs);
}
}
for &(mvdx, mvdy) in &mvds[..n_mvd] {
w.write_se(mvdx);
w.write_se(mvdy);
}
write_cbp_inter(w, cbp);
if cbp != 0 {
w.write_se(self.qp_delta()); }
self.nnz_cache_load(mb_x, mb_y);
drop(_g_syn);
let _g_scan = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Scatter);
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if cbp_luma & (1 << (blk / 4)) != 0 {
let nc = self.nc_pred(lbx, lby);
let scan16 = scan_4x4_dcac_i16(&dct[blk * 16..blk * 16 + 16]);
encode_residual_block(w, &scan16, 16, nc) as u8
} else {
0
};
self.nnz_cache_set(lbx, lby, total);
self.nnz_y[by * w4 + bx] = total;
}
if cbp_chroma != 0 {
for c in 0..2 {
encode_residual_block(w, &c_dc_levels[c], 4, -1);
}
}
if cbp_chroma == 2 {
self.chroma_cache_load(mb_x, mb_y);
let w2 = self.mb_w * 2;
for c in 0..2 {
for &(bx, by) in &CHROMA_4X4_SCAN_XY {
let nc = self.chroma_nc_pred(c, bx, by);
let ac = scan_4x4_ac(&c_q[c][by * 2 + bx]);
let total = encode_residual_block(w, &ac, 15, nc) as u8;
self.chroma_nnz_cache_set(c, bx, by, total);
self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
}
}
}
drop(_g_scan);
let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
#[repr(align(16))]
struct Align16([i16; 64]);
let mut dct_in = Align16([0i16; 64]);
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
let rec_off = base + qy * self.cw + qx;
if cbp_luma & (1 << qi) == 0 {
for r in 0..8 {
let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
}
continue;
}
for k in 0..4 {
let blk = qi * 4 + k;
let mut lvl = [0i32; 16];
for i in 0..16 {
lvl[i] = dct[blk * 16 + i] as i32;
}
let deq = dequantize(&lvl, qp);
for i in 0..16 {
dct_in.0[k * 16 + i] = deq[i] as i16;
}
}
rusty_h264_accel::idct_four_t4_rec(
&mut self.rec_y[rec_off..],
self.cw,
&pred_y[qy * 16 + qx..],
16,
&dct_in.0,
);
}
for c in 0..2 {
let base_c = (mb_y * 8) * self.ccw + mb_x * 8;
let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
if cbp_chroma == 0 {
for r in 0..8 {
let dsti = base_c + r * self.ccw;
plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
}
} else {
#[repr(align(16))]
struct A([i16; 64]);
let mut d = A([0i16; 64]);
for i in 0..4 {
let deq = dequantize(&c_q[c][i], qpc);
for j in 0..16 {
d.0[i * 16 + j] = deq[j] as i16;
}
d.0[i * 16] = c_recon_dc[c][i] as i16;
}
rusty_h264_accel::idct_four_t4_rec(&mut plane[base_c..], self.ccw, &c_pred[c], 8, &d.0);
}
}
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
}
}
}
#[allow(clippy::too_many_arguments)]
fn encode_inter_mb_v1(
&mut self,
w: &mut BitWriter,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
) {
self.encode_inter_mb_v1_b(w, refs, sy, su, sv, mb_x, mb_y, mode, parts, None);
}
#[allow(clippy::too_many_arguments)]
fn plan_inter_mb(
&mut self,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
bspec: Option<BInter>,
) -> InterPlan {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(1);
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncInterCode);
let (qp, qpc) = (self.qp, self.qpc);
let w4 = self.mb_w * 4;
let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
let mut pred_y = [0u8; 256];
let mut c_pred = [[0u8; 64]; 2];
let mut mvds = [(0i32, 0i32); 4]; let mut plan_refs = [0i32; 4]; let mut n_mvd = 0;
let _g_mc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
if let Some(b) = bspec.filter(|b| b.dir == 0) {
let (dp, dc, motion) = self.b_direct(&refs[0], b.l1, mb_x, mb_y);
pred_y = dp;
c_pred = dc;
self.commit_direct_motion(mb_x, mb_y, &motion);
} else if let Some(b) = bspec {
let use0 = b.dir == 1 || b.dir == 3;
let use1 = b.dir == 2 || b.dir == 3;
let (lx, ly) = (mb_x * 16, mb_y * 16);
let (cx, cy) = (mb_x * 8, mb_y * 8);
let (pbx, pby) = ((mb_x * 4) as isize, (mb_y * 4) as isize);
if use0 {
let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 0);
let p = predict_partition_mv(0, 0, a, c0, c1, 0);
mvds[n_mvd] = (b.mv0.0 - p.0, b.mv0.1 - p.1);
n_mvd += 1;
}
if use1 {
let [a, c0, c1] = self.mv_neighbors_block_list(pbx, pby, 4, 1);
let p = predict_partition_mv(0, 0, a, c0, c1, 0);
mvds[n_mvd] = (b.mv1.0 - p.0, b.mv1.1 - p.1);
n_mvd += 1;
}
let mut a_y = [0u8; 256];
let mut b_y = [0u8; 256];
let mut a_c = [[0u8; 64]; 2];
let mut b_c = [[0u8; 64]; 2];
if use0 {
mc_luma(&refs[0].y, self.cw, ch, lx, ly, 16, 16, b.mv0.0, b.mv0.1, &mut a_y);
mc_chroma(&refs[0].u, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[0]);
mc_chroma(&refs[0].v, self.ccw, cch, cx, cy, 8, 8, b.mv0.0, b.mv0.1, &mut a_c[1]);
}
if use1 {
mc_luma(&b.l1.y, self.cw, ch, lx, ly, 16, 16, b.mv1.0, b.mv1.1, &mut b_y);
mc_chroma(&b.l1.u, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[0]);
mc_chroma(&b.l1.v, self.ccw, cch, cx, cy, 8, 8, b.mv1.0, b.mv1.1, &mut b_c[1]);
}
match (use0, use1) {
(true, true) => {
for i in 0..256 {
pred_y[i] = bi_blend(a_y[i] as i32, b_y[i] as i32, self.bi_w);
}
for c in 0..2 {
for i in 0..64 {
c_pred[c][i] = bi_blend(a_c[c][i] as i32, b_c[c][i] as i32, self.bi_w);
}
}
}
(true, false) => {
pred_y = a_y;
c_pred = a_c;
}
_ => {
pred_y = b_y;
c_pred = b_c;
}
}
for by in 0..4 {
for bx in 0..4 {
let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
self.inter_y[idx] = true;
self.coded_y[idx] = true;
self.mv_y[idx] = if use0 { b.mv0 } else { (0, 0) };
self.ref_idx_y[idx] = if use0 { 0 } else { -1 };
self.mv1_y[idx] = if use1 { b.mv1 } else { (0, 0) };
self.ref_idx1_y[idx] = if use1 { 0 } else { -1 };
}
}
} else {
for (part, &(rx, ry, rw, rh)) in inter_partitions(mode).iter().enumerate() {
let (refi, mv) = parts[part];
plan_refs[part] = refi; let reference = &refs[refi as usize];
let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
let pmv = predict_partition_mv(mode, part, a, b, c, refi);
mvds[n_mvd] = (mv.0 - pmv.0, mv.1 - pmv.1);
n_mvd += 1;
for by in ry / 4..ry / 4 + rh / 4 {
for bx in rx / 4..rx / 4 + rw / 4 {
let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
self.mv_y[idx] = mv;
self.inter_y[idx] = true;
self.ref_idx_y[idx] = refi;
self.coded_y[idx] = true;
}
}
if rw == 16 && rh == 16 {
self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
} else {
let mut tmp = [0u8; 256];
self.mc_luma_cached(reference, mb_x * 16 + rx, mb_y * 16 + ry, rw, rh, mv.0, mv.1, &mut tmp);
if rw == 8 {
for dy in 0..rh {
pred_y[(ry + dy) * 16 + rx..][..8].copy_from_slice(&tmp[dy * 8..][..8]);
}
} else {
for dy in 0..rh {
pred_y[(ry + dy) * 16 + rx..][..16].copy_from_slice(&tmp[dy * 16..][..16]);
}
}
}
let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
for cc in 0..2 {
let rc = if cc == 0 { &reference.u } else { &reference.v };
if crw == 8 && crh == 8 {
mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut c_pred[cc]);
} else {
let mut tc = [0u8; 64];
mc_chroma(rc, self.ccw, cch, mb_x * 8 + crx, mb_y * 8 + cry, crw, crh, mv.0, mv.1, &mut tc);
if crw == 4 {
for dy in 0..crh {
c_pred[cc][(cry + dy) * 8 + crx..][..4].copy_from_slice(&tc[dy * 4..][..4]);
}
} else {
for dy in 0..crh {
c_pred[cc][(cry + dy) * 8 + crx..][..8].copy_from_slice(&tc[dy * 8..][..8]);
}
}
}
}
}
}
let mut q_blocks = [[0i32; 16]; 16]; let mut cbp_luma = 0u32;
#[allow(unused_mut)]
let mut t8x8 = false;
#[allow(unused_mut)]
let mut q8 = [[0i32; 64]; 4];
drop(_g_mc);
let _g_tq = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncTq);
#[cfg(accel)]
{
let mut dctw = AlignedDct([0i16; 256]);
let dct = &mut dctw.0;
let base = mb_y * 16 * self.cw + mb_x * 16;
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
rusty_h264_accel::dct_four_t4(
&mut dct[qi * 64..qi * 64 + 64],
&sy[base + qy * self.cw + qx..],
self.cw,
&pred_y[qy * 16 + qx..],
16,
);
}
let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
for qi in 0..4 {
rusty_h264_accel::quant_four_4x4(&mut dct[qi * 64..qi * 64 + 64], &ff, mf);
}
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let mut nz = false;
for i in 0..16 {
let v = dct[blk * 16 + i] as i32;
q_blocks[lby * 4 + lbx][i] = v;
nz |= v != 0;
}
if nz {
cbp_luma |= 1 << (blk / 4);
}
}
}
#[cfg(not(accel))]
{
let mut res_blocks = [[0i32; 16]; 16]; for lby in 0..4 {
for lbx in 0..4 {
let b = &mut res_blocks[lby * 4 + lbx];
for dy in 0..4 {
for dx in 0..4 {
let sx = mb_x * 16 + lbx * 4 + dx;
let syy = mb_y * 16 + lby * 4 + dy;
b[dy * 4 + dx] = sy[syy * self.cw + sx] as i32
- pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
}
}
}
}
let mut coeffs = [[0i32; 16]; 16];
forward_dct_blocks(&res_blocks, &mut coeffs);
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let q = rdoq(&coeffs[lby * 4 + lbx], qp, 6, self.rdoq_strength, 0);
if q.iter().any(|&v| v != 0) {
cbp_luma |= 1 << (blk / 4);
}
q_blocks[lby * 4 + lbx] = q;
}
}
{
if self.transform_8x8 && self.inter8x8 != 0 {
let lambda =
0.85 * self.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let mut ssd4 = 0i64;
let mut rate4 = 0f64;
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let mut predb = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
predb[dy * 4 + dx] =
pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
}
}
let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
let s = reconstruct_4x4(&deq, &predb);
for dy in 0..4 {
for dx in 0..4 {
let sx = mb_x * 16 + lbx * 4 + dx;
let syy = mb_y * 16 + lby * 4 + dy;
let d = s[dy * 4 + dx] as i64 - sy[syy * self.cw + sx] as i64;
ssd4 += d * d;
}
}
for &l in &q_blocks[lby * 4 + lbx] {
if l != 0 {
rate4 += rdoq_rate((l as i64).abs());
}
}
}
let (q8c, cbp8, rate8, _rec8, ssd8) =
plan_inter8_luma(sy, self.cw, mb_x, mb_y, &pred_y, qp);
let j4 = ssd4 as f64 + lambda * (rate4 + 16.0);
let j8 = ssd8 as f64 + lambda * (rate8 + 16.0 + self.inter8_pen as f64);
if cbp8 > 0 && j8 < j4 {
t8x8 = true;
cbp_luma = cbp8;
q8 = q8c;
}
}
}
let mut c_dc_levels = [[0i32; 4]; 2];
let mut c_recon_dc = [[0i32; 4]; 2];
let mut c_q = [[[0i32; 16]; 4]; 2];
let (mut any_ac, mut any_dc) = (false, false);
for c in 0..2 {
let src = if c == 0 { su } else { sv };
#[cfg(accel)]
let (mut dc2x2, applied) = {
#[repr(align(16))]
struct A([i16; 64]);
let mut dct = A([0i16; 64]);
rusty_h264_accel::dct_four_t4(
&mut dct.0,
&src[(mb_y * 8) * self.ccw + mb_x * 8..],
self.ccw,
&c_pred[c],
8,
);
let dc = [
dct.0[0] as i32,
dct.0[16] as i32,
dct.0[32] as i32,
dct.0[48] as i32,
];
let ffc = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
let mfc = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
rusty_h264_accel::quant_four_4x4(&mut dct.0, &ffc, mfc);
for i in 0..4 {
let q = &mut c_q[c][i];
q[0] = 0;
for j in 1..16 {
let v = dct.0[i * 16 + j] as i32;
q[j] = v;
if v != 0 {
any_ac = true;
}
}
}
(dc, true)
};
#[cfg(not(accel))]
let (mut dc2x2, applied) = ([0i32; 4], false);
if !applied {
let mut res_blocks = [[0i32; 16]; 4];
for by in 0..2 {
for bx in 0..2 {
let b = &mut res_blocks[by * 2 + bx];
for dy in 0..4 {
for dx in 0..4 {
let sx = mb_x * 8 + bx * 4 + dx;
let syy = mb_y * 8 + by * 4 + dy;
b[dy * 4 + dx] = src[syy * self.ccw + sx] as i32
- c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
}
}
}
}
let mut coeffs = [[0i32; 16]; 4];
forward_dct_blocks(&res_blocks, &mut coeffs);
for i in 0..4 {
dc2x2[i] = coeffs[i][0];
let mut q = rdoq(&coeffs[i], qpc, 6, self.rdoq_strength, 1);
q[0] = 0;
if q[1..].iter().any(|&v| v != 0) {
any_ac = true;
}
c_q[c][i] = q;
}
}
let dl = forward_quant_chroma_dc(&dc2x2, qpc, false);
if dl.iter().any(|&v| v != 0) {
any_dc = true;
}
c_recon_dc[c] = inverse_quant_chroma_dc(&dl, qpc);
c_dc_levels[c] = dl;
}
let cbp_chroma: u32 = if any_ac { 2 } else if any_dc { 1 } else { 0 };
let cbp = cbp_luma | (cbp_chroma << 4);
drop(_g_tq);
let _g_rec = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
#[cfg(accel)]
if t8x8 {
let weight = [16i32; 64];
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
let predb: [i32; 64] = std::array::from_fn(|i| {
pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
});
let recon = add_residual_8x8(&res_r, &predb);
for dy in 0..8 {
for dx in 0..8 {
let px = mb_x * 16 + b8x * 8 + dx;
let py = mb_y * 16 + b8y * 8 + dy;
self.rec_y[py * self.cw + px] = recon[dy * 8 + dx];
}
}
}
} else {
#[repr(align(16))]
struct Align16([i16; 64]);
let mut dct_in = Align16([0i16; 64]);
let base = mb_y * 16 * self.cw + mb_x * 16;
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
let rec_off = base + qy * self.cw + qx;
if cbp_luma & (1 << qi) == 0 {
for r in 0..8 {
let (dsti, srci) = (rec_off + r * self.cw, (qy + r) * 16 + qx);
self.rec_y[dsti..dsti + 8].copy_from_slice(&pred_y[srci..srci + 8]);
}
continue;
}
for k in 0..4 {
let blk = qi * 4 + k;
let (lbx, lby) = LUMA_4X4_SCAN_XY[blk];
let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
for i in 0..16 {
dct_in.0[k * 16 + i] = deq[i] as i16;
}
}
rusty_h264_accel::idct_four_t4_rec(
&mut self.rec_y[rec_off..],
self.cw,
&pred_y[qy * 16 + qx..],
16,
&dct_in.0,
);
}
}
#[cfg(not(accel))]
if t8x8 {
let weight = [16i32; 64];
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let res_r = inverse_quant_8x8(&q8[b8], qp, &weight);
let predb: [i32; 64] = std::array::from_fn(|i| {
pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
});
let recon = add_residual_8x8(&res_r, &predb);
for dy in 0..8 {
for dx in 0..8 {
let px = mb_x * 16 + b8x * 8 + dx;
let py = mb_y * 16 + b8y * 8 + dy;
self.rec_y[py * self.cw + px] = recon[dy * 8 + dx];
}
}
}
} else {
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let mut predb = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
predb[dy * 4 + dx] = pred_y[(lby * 4 + dy) * 16 + (lbx * 4 + dx)] as i32;
}
}
let deq = dequantize(&q_blocks[lby * 4 + lbx], qp);
let s = reconstruct_4x4(&deq, &predb);
store(&mut self.rec_y, self.cw, mb_x * 16 + lbx * 4, mb_y * 16 + lby * 4, &s);
}
}
for c in 0..2 {
#[cfg(accel)]
{
let base = (mb_y * 8) * self.ccw + mb_x * 8;
let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
if cbp_chroma == 0 {
for r in 0..8 {
let dsti = base + r * self.ccw;
plane[dsti..dsti + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
}
} else {
#[repr(align(16))]
struct A([i16; 64]);
let mut d = A([0i16; 64]);
for i in 0..4 {
let deq = dequantize(&c_q[c][i], qpc);
for j in 0..16 {
d.0[i * 16 + j] = deq[j] as i16;
}
d.0[i * 16] = c_recon_dc[c][i] as i16;
}
rusty_h264_accel::idct_four_t4_rec(&mut plane[base..], self.ccw, &c_pred[c], 8, &d.0);
}
}
#[cfg(not(accel))]
{
let mut deq_blocks = [[0i32; 16]; 4];
for i in 0..4 {
deq_blocks[i] = dequantize(&c_q[c][i], qpc);
deq_blocks[i][0] = c_recon_dc[c][i];
}
let mut res = [[0i32; 16]; 4];
inverse_dct_blocks(&deq_blocks, &mut res);
let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
for by in 0..2 {
for bx in 0..2 {
let mut predb = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
predb[dy * 4 + dx] = c_pred[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
}
}
let s = add_residual_4x4(&res[by * 2 + bx], &predb);
store(plane, self.ccw, mb_x * 8 + bx * 4, mb_y * 8 + by * 4, &s);
}
}
}
}
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
self.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
}
InterPlan { mvds, plan_refs, n_mvd, cbp, q_blocks, c_dc_levels, c_q, t8x8, q8 }
}
#[allow(clippy::too_many_arguments)]
fn encode_inter_mb_v1_b(
&mut self,
w: &mut BitWriter,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
bspec: Option<BInter>,
) {
let plan = self.plan_inter_mb(refs, sy, su, sv, mb_x, mb_y, mode, parts, bspec);
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
self.emit_inter_cavlc(w, refs.len(), mb_x, mb_y, mode, parts, bspec, &plan);
}
#[allow(clippy::too_many_arguments)]
fn emit_inter_cavlc(
&mut self,
w: &mut BitWriter,
num_refs: usize,
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
bspec: Option<BInter>,
plan: &InterPlan,
) {
let w4 = self.mb_w * 4;
let (cbp, cbp_luma, cbp_chroma) = (plan.cbp, plan.cbp & 15, plan.cbp >> 4);
w.write_ue(bspec.map_or(mode as u32, |b| b.dir as u32)); if mode == 3 {
for _ in 0..4 {
w.write_ue(0);
}
}
if num_refs > 1 {
for &(refi, _) in parts {
write_ref_idx(w, refi, num_refs);
}
}
for &(mvdx, mvdy) in &plan.mvds[..plan.n_mvd] {
w.write_se(mvdx);
w.write_se(mvdy);
}
write_cbp_inter(w, cbp);
if cbp_luma > 0 && self.transform_8x8 {
w.write_bit(plan.t8x8);
}
if cbp != 0 {
w.write_se(self.qp_delta()); }
self.nnz_cache_load(mb_x, mb_y);
if plan.t8x8 {
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let scan8 = scan_8x8_fwd(&plan.q8[b8]);
for sub in 0..4usize {
let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
let total = if cbp_luma & (1 << b8) != 0 {
let nc = self.nc_pred(cx, cy);
let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
encode_residual_block(w, &blk, 16, nc) as u8
} else {
0
};
self.nnz_cache_set(cx, cy, total);
self.nnz_y[by * w4 + bx] = total;
}
}
} else {
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if cbp_luma & (1 << (blk / 4)) != 0 {
let nc = self.nc_pred(lbx, lby);
let scan16 = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
encode_residual_block(w, &scan16, 16, nc) as u8
} else {
0
};
self.nnz_cache_set(lbx, lby, total);
self.nnz_y[by * w4 + bx] = total;
}
}
if cbp_chroma != 0 {
for c in 0..2 {
encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
}
}
if cbp_chroma == 2 {
self.chroma_cache_load(mb_x, mb_y);
let w2 = self.mb_w * 2;
for c in 0..2 {
for &(bx, by) in &CHROMA_4X4_SCAN_XY {
let nc = self.chroma_nc_pred(c, bx, by);
let ac = scan_4x4_ac(&plan.c_q[c][by * 2 + bx]);
let total = encode_residual_block(w, &ac, 15, nc) as u8;
self.chroma_nnz_cache_set(c, bx, by, total);
self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
}
}
}
}
#[inline]
fn mc_luma_cached(
&self,
reference: &crate::RefFrame,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) {
let ch = self.mb_h * 16;
let cw = self.cw;
if !self.fast {
let p = reference.hpel(cw, ch);
if rusty_h264_common::inter::hpel_block(p, x0, y0, bw, bh, mvx, mvy, out) {
return;
}
if let Some((plane, base, stride)) =
rusty_h264_common::inter::hpel_ref(p, x0, y0, bw, bh, mvx, mvy)
{
for r in 0..bh {
out[r * bw..r * bw + bw].copy_from_slice(&plane[base + r * stride..][..bw]);
}
return;
}
}
mc_luma(&reference.y, cw, ch, x0, y0, bw, bh, mvx, mvy, out);
}
fn skip_predict_luma(
&self,
refs: &[crate::RefFrame],
mb_x: usize,
mb_y: usize,
mv: (i32, i32),
) -> [u8; 256] {
#[cfg(feature = "profile")]
let _site = rusty_h264_common::inter::mcstats::SiteTag::new(3);
let reference = &refs[0]; let ch = self.mb_h * 16;
let mut pred_y = [0u8; 256];
self.mc_luma_cached(reference, mb_x * 16, mb_y * 16, 16, 16, mv.0, mv.1, &mut pred_y);
pred_y
}
fn skip_predict_chroma(
&self,
refs: &[crate::RefFrame],
mb_x: usize,
mb_y: usize,
mv: (i32, i32),
) -> [[u8; 64]; 2] {
let reference = &refs[0];
let cch = self.mb_h * 8;
let mut pred_c = [[0u8; 64]; 2];
for c in 0..2 {
let rc = if c == 0 { &reference.u } else { &reference.v };
mc_chroma(rc, self.ccw, cch, mb_x * 8, mb_y * 8, 8, 8, mv.0, mv.1, &mut pred_c[c]);
}
pred_c
}
fn skip_luma_is_free(&self, sy: &[u8], mb_x: usize, mb_y: usize, pred_y: &[u8; 256]) -> bool {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
let qp = self.qp;
#[cfg(accel)]
if self.skip_accel_check {
#[repr(align(16))]
struct Align16([i16; 64]);
let mut dct = Align16([0i16; 64]);
let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
rusty_h264_accel::dct_four_t4(
&mut dct.0,
&sy[(mb_y * 16 + qy) * self.cw + mb_x * 16 + qx..],
self.cw,
&pred_y[qy * 16 + qx..],
16,
);
rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
if dct.0.iter().any(|&v| v != 0) {
return false;
}
}
return true;
}
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
let ff = rusty_h264_common::transform::quant_dz_ff(qp, 6);
let mut t_min = i32::MAX;
for p in 0..8 {
let t = (65536 + mf[p] as i32 - 1) / mf[p] as i32 - ff[p] as i32;
t_min = t_min.min(t);
}
let t_dc = (65536 + mf[0] as i32 - 1) / mf[0] as i32 - ff[0] as i32;
for by in 0..4 {
for bx in 0..4 {
let mut res = [0i32; 16];
let (mut sad, mut dc) = (0i32, 0i32);
for dy in 0..4 {
for dx in 0..4 {
let sx = mb_x * 16 + bx * 4 + dx;
let syy = mb_y * 16 + by * 4 + dy;
let d = sy[syy * self.cw + sx] as i32
- pred_y[(by * 4 + dy) * 16 + (bx * 4 + dx)] as i32;
res[dy * 4 + dx] = d;
sad += d.abs();
dc += d;
}
}
if 4 * sad < t_min {
continue; }
if dc.abs() >= t_dc {
return false; }
if quantize(&forward_core(&res), qp, 6).iter().any(|&v| v != 0) {
return false;
}
}
}
true
}
fn skip_chroma_is_free(
&self,
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
pred_c: &[[u8; 64]; 2],
) -> bool {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFree);
let qpc = self.qpc;
#[cfg(accel)]
if self.skip_accel_check {
#[repr(align(16))]
struct Align16C([i16; 64]);
let mut dct = Align16C([0i16; 64]);
let ff = rusty_h264_common::transform::quant_dz_ff(qpc, 6);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
for c in 0..2 {
let src = if c == 0 { su } else { sv };
rusty_h264_accel::dct_four_t4(
&mut dct.0,
&src[(mb_y * 8) * self.ccw + mb_x * 8..],
self.ccw,
&pred_c[c],
8,
);
let dc2x2 = [
dct.0[0] as i32,
dct.0[16] as i32,
dct.0[32] as i32,
dct.0[48] as i32,
];
rusty_h264_accel::quant_four_4x4(&mut dct.0, &ff, mf);
for b in 0..4 {
if dct.0[b * 16 + 1..b * 16 + 16].iter().any(|&v| v != 0) {
return false;
}
}
if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
return false;
}
}
return true;
}
for c in 0..2 {
let src = if c == 0 { su } else { sv };
let mut dc2x2 = [0i32; 4];
for &(bx, by) in &CHROMA_4X4_SCAN_XY {
let mut res = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
let sx = mb_x * 8 + bx * 4 + dx;
let syy = mb_y * 8 + by * 4 + dy;
res[dy * 4 + dx] = src[syy * self.ccw + sx] as i32
- pred_c[c][(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
}
}
let coeffs = forward_core(&res);
dc2x2[by * 2 + bx] = coeffs[0];
if quantize(&coeffs, qpc, 6)[1..].iter().any(|&v| v != 0) {
return false;
}
}
if forward_quant_chroma_dc(&dc2x2, qpc, false).iter().any(|&v| v != 0) {
return false;
}
}
true
}
#[allow(clippy::too_many_arguments)]
fn pred_ssd(
&self,
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
pred_y: &[u8; 256],
pred_c: &[[u8; 64]; 2],
) -> i64 {
let mut ssd = 0i64;
for dy in 0..16 {
for dx in 0..16 {
let d = sy[(mb_y * 16 + dy) * self.cw + mb_x * 16 + dx] as i64
- pred_y[dy * 16 + dx] as i64;
ssd += d * d;
}
}
for c in 0..2 {
let src = if c == 0 { su } else { sv };
for dy in 0..8 {
for dx in 0..8 {
let d = src[(mb_y * 8 + dy) * self.ccw + mb_x * 8 + dx] as i64
- pred_c[c][dy * 8 + dx] as i64;
ssd += d * d;
}
}
}
ssd
}
fn mb_ssd(&self, sy: &[u8], su: &[u8], sv: &[u8], mb_x: usize, mb_y: usize) -> i64 {
let mut ssd = 0i64;
for dy in 0..16 {
for dx in 0..16 {
let i = (mb_y * 16 + dy) * self.cw + mb_x * 16 + dx;
let d = sy[i] as i64 - self.rec_y[i] as i64;
ssd += d * d;
}
}
for c in 0..2 {
let (src, rec) = if c == 0 { (su, &self.rec_u) } else { (sv, &self.rec_v) };
for dy in 0..8 {
for dx in 0..8 {
let i = (mb_y * 8 + dy) * self.ccw + mb_x * 8 + dx;
let d = src[i] as i64 - rec[i] as i64;
ssd += d * d;
}
}
}
ssd
}
#[allow(clippy::too_many_arguments)]
fn commit_skip_probe_marker(&self) {}
fn commit_skip(
&mut self,
mb_x: usize,
mb_y: usize,
mv: (i32, i32),
pred_y: &[u8; 256],
pred_c: &[[u8; 64]; 2],
) {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
let base = mb_y * 16 * self.cw + mb_x * 16;
for r in 0..16 {
let d = base + r * self.cw;
self.rec_y[d..d + 16].copy_from_slice(&pred_y[r * 16..r * 16 + 16]);
}
let cbase = mb_y * 8 * self.ccw + mb_x * 8;
for c in 0..2 {
let plane = if c == 0 { &mut self.rec_u } else { &mut self.rec_v };
for r in 0..8 {
let d = cbase + r * self.ccw;
plane[d..d + 8].copy_from_slice(&pred_c[c][r * 8..r * 8 + 8]);
}
}
self.set_mb_mv(mb_x, mb_y, mv, true, 0);
let w4 = self.mb_w * 4;
for row in 0..4 {
let st = (mb_y * 4 + row) * w4 + mb_x * 4;
self.modes_y[st..st + 4].fill(2);
self.coded_y[st..st + 4].fill(true);
}
}
#[allow(clippy::too_many_arguments)]
fn trial_inter(
&mut self,
refs: &[crate::RefFrame],
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
mode: u8,
parts: &[(i32, (i32, i32))],
) -> (i64, usize) {
let snap = self.save_mb(mb_x, mb_y);
let mut scratch = BitWriter::new();
self.encode_inter_mb(&mut scratch, refs, sy, su, sv, mb_x, mb_y, mode, parts);
let bits = scratch.bit_len();
let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
self.load_mb(mb_x, mb_y, &snap);
(ssd, bits)
}
fn trial_intra(
&mut self,
sy: &[u8],
su: &[u8],
sv: &[u8],
mb_x: usize,
mb_y: usize,
is_p: bool,
) -> (i64, usize) {
let snap = self.save_mb(mb_x, mb_y);
let mut scratch = BitWriter::new();
encode_mb(self, &mut scratch, mb_x, mb_y, sy, su, sv, is_p);
let bits = scratch.bit_len();
let ssd = self.mb_ssd(sy, su, sv, mb_x, mb_y);
self.load_mb(mb_x, mb_y, &snap);
(ssd, bits)
}
#[allow(clippy::too_many_arguments)]
fn best_part(
&self,
refs: &[crate::RefFrame],
sy: &[u8],
nb: &[MvNeighbor; 3],
num_refs: usize,
rx: usize,
ry: usize,
rw: usize,
rh: usize,
extra: &[(i32, i32)],
lme: f64,
) -> (i32, (i32, i32), i64) {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
let [a, b, c] = *nb;
let (mut br, mut bmv, mut bc) = (0i32, (0, 0), i64::MAX);
for r in 0..num_refs {
let mut seeds = vec![predict_mv(a, b, c, r as i32)];
seeds.extend_from_slice(extra);
let (mv, cost) = self.motion_search(&refs[r], sy, rx, ry, rw, rh, &seeds, lme, None);
let cost = cost + (lme * ref_bits(r, num_refs) as f64) as i64;
if cost < bc {
bc = cost;
br = r as i32;
bmv = mv;
}
}
(br, bmv, bc)
}
#[allow(clippy::too_many_arguments)]
fn refine_part(
&self,
refs: &[crate::RefFrame],
sy: &[u8],
nb: &[MvNeighbor; 3],
num_refs: usize,
rx: usize,
ry: usize,
rw: usize,
rh: usize,
lme: f64,
r: i32,
mv: (i32, i32),
) -> ((i32, i32), i64) {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMe);
let [a, b, c] = *nb;
let rb = (lme * ref_bits(r as usize, num_refs) as f64) as i64;
let seeds = [predict_mv(a, b, c, r)];
let (m, cc) = self.motion_search(&refs[r as usize], sy, rx, ry, rw, rh, &seeds, lme, Some(mv));
(m, cc + rb)
}
fn best_i16_sad(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
let (lx, ly) = (mb_x * 16, mb_y * 16);
let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
let mut top = [0u8; 16];
let mut left = [0u8; 16];
if avail_top {
for i in 0..16 {
top[i] = self.rec_y[(ly - 1) * self.cw + lx + i];
}
}
if avail_left {
for i in 0..16 {
left[i] = self.rec_y[(ly + i) * self.cw + lx - 1];
}
}
let corner = if avail_top && avail_left {
self.rec_y[(ly - 1) * self.cw + lx - 1]
} else {
0
};
let mut best = i64::MAX;
for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
if !mode.available(avail_top, avail_left) {
continue;
}
let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
best = best.min(sad_16x16(sy, self.cw, lx, ly, &pred));
}
best
}
fn best_i16_satd(&self, sy: &[u8], mb_x: usize, mb_y: usize) -> i64 {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCost);
let (lx, ly) = (mb_x * 16, mb_y * 16);
let (avail_top, avail_left) = (mb_y > 0, mb_x > 0);
let mut top = [0u8; 16];
let mut left = [0u8; 16];
if avail_top {
for i in 0..16 {
top[i] = self.rec_y[(ly - 1) * self.cw + lx + i];
}
}
if avail_left {
for i in 0..16 {
left[i] = self.rec_y[(ly + i) * self.cw + lx - 1];
}
}
let corner = if avail_top && avail_left {
self.rec_y[(ly - 1) * self.cw + lx - 1]
} else {
0
};
let mut best = i64::MAX;
for mode in [I16Mode::Dc, I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
if !mode.available(avail_top, avail_left) {
continue;
}
let pred = i16_pred(self, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
best = best.min(satd_16x16(sy, self.cw, lx, ly, &pred));
}
best
}
fn save_mb(&self, mb_x: usize, mb_y: usize) -> MbState {
let mut d = MbState::default();
self.save_mb_into(mb_x, mb_y, &mut d);
d
}
fn save_mb_into(&self, mb_x: usize, mb_y: usize, d: &mut MbState) {
let w4 = self.mb_w * 4;
let w2 = self.mb_w * 2;
macro_rules! reg4 {
($v:expr, $o:expr) => {{
$o.clear();
for dy in 0..4 {
for dx in 0..4 {
$o.push($v[(mb_y * 4 + dy) * w4 + mb_x * 4 + dx]);
}
}
}};
}
macro_rules! regn {
($v:expr, $o:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {{
$o.clear();
for dy in 0..$n {
for dx in 0..$n {
$o.push($v[($oy + dy) * $stride + $ox + dx]);
}
}
}};
}
regn!(self.rec_y, d.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
regn!(self.rec_u, d.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
regn!(self.rec_v, d.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
reg4!(self.nnz_y, d.nnz_y);
regn!(self.nnz_c[0], d.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
regn!(self.nnz_c[1], d.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
reg4!(self.mv_y, d.mv_y);
reg4!(self.inter_y, d.inter_y);
reg4!(self.ref_idx_y, d.ref_idx_y);
reg4!(self.coded_y, d.coded_y);
reg4!(self.modes_y, d.modes_y);
d.cur_qp = self.cur_qp;
}
fn load_mb(&mut self, mb_x: usize, mb_y: usize, s: &MbState) {
let w4 = self.mb_w * 4;
let w2 = self.mb_w * 2;
macro_rules! put4 {
($v:expr, $src:expr) => {
for dy in 0..4 {
for dx in 0..4 {
$v[(mb_y * 4 + dy) * w4 + mb_x * 4 + dx] = $src[dy * 4 + dx];
}
}
};
}
macro_rules! putn {
($v:expr, $src:expr, $n:expr, $ox:expr, $oy:expr, $stride:expr) => {
for dy in 0..$n {
for dx in 0..$n {
$v[($oy + dy) * $stride + $ox + dx] = $src[dy * $n + dx];
}
}
};
}
putn!(self.rec_y, s.rec_y, 16, mb_x * 16, mb_y * 16, self.cw);
putn!(self.rec_u, s.rec_u, 8, mb_x * 8, mb_y * 8, self.ccw);
putn!(self.rec_v, s.rec_v, 8, mb_x * 8, mb_y * 8, self.ccw);
put4!(self.nnz_y, s.nnz_y);
putn!(self.nnz_c[0], s.nnz_c[0], 2, mb_x * 2, mb_y * 2, w2);
putn!(self.nnz_c[1], s.nnz_c[1], 2, mb_x * 2, mb_y * 2, w2);
put4!(self.mv_y, s.mv_y);
put4!(self.inter_y, s.inter_y);
put4!(self.ref_idx_y, s.ref_idx_y);
put4!(self.coded_y, s.coded_y);
put4!(self.modes_y, s.modes_y);
self.cur_qp = s.cur_qp;
}
fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
let w4 = self.mb_w * 4;
for lbx in 0..4 {
self.nnz_l_cache[1 + lbx] = if mb_y == 0 {
0x80
} else {
self.nnz_y[(mb_y * 4 - 1) * w4 + (mb_x * 4 + lbx)]
};
}
for lby in 0..4 {
self.nnz_l_cache[(lby + 1) * 5] = if mb_x == 0 {
0x80
} else {
self.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 - 1)]
};
}
}
#[inline]
fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32; let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32; let r = left + top;
if r < 0x80 {
(r + 1) >> 1
} else {
r & 0x7f
}
}
#[inline]
fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
self.nnz_l_cache[(lby + 1) * 5 + (lbx + 1)] = total;
}
fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
let w2 = self.mb_w * 2;
for c in 0..2 {
for bx in 0..2 {
self.nnz_c_cache[c][1 + bx] = if mb_y == 0 {
0x80
} else {
self.nnz_c[c][(mb_y * 2 - 1) * w2 + (mb_x * 2 + bx)]
};
}
for by in 0..2 {
self.nnz_c_cache[c][(by + 1) * 3] = if mb_x == 0 {
0x80
} else {
self.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 - 1)]
};
}
}
}
#[inline]
fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
let r = left + top;
if r < 0x80 {
(r + 1) >> 1
} else {
r & 0x7f
}
}
#[inline]
fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
self.nnz_c_cache[c][(by + 1) * 3 + (bx + 1)] = total;
}
}
#[inline(never)]
fn derive_mb_bs_from(
fe: &FrameEncoder,
mb_x: usize,
mb_y: usize,
kind: rusty_h264_common::deblock::MbKind,
) -> rusty_h264_common::deblock::MbBs {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncBs);
let view = rusty_h264_common::deblock::BlockInfo {
inter: &fe.inter_y,
nnz: &fe.nnz_y,
mv: &fe.mv_y,
ref_id: &fe.ref_idx_y,
mv1: &[],
ref_id1: &[],
w4: fe.mb_w * 4,
t8x8: &[],
bs: &[],
};
rusty_h264_common::deblock::derive_mb_kind(&view, mb_x, mb_y, kind)
}
pub fn encode_slice_data(
w: &mut BitWriter,
cfg: &EncoderConfig,
frame: &YuvFrame,
qp: u8,
is_p: bool,
refs: &[crate::RefFrame],
qpo: &[i32],
) -> crate::RefFrame {
let _g_prep = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncPrep);
let mut fe = FrameEncoder::new(cfg);
let precomp = rusty_h264_common::deblock::precomputed_bs_enabled();
let mut bs_grid =
vec![rusty_h264_common::deblock::MbBs::UNSET; if precomp { fe.mb_w * fe.mb_h } else { 0 }];
fe.qp = qp;
fe.qpc = chroma_qp(qp);
fe.cur_qp = qp;
if cfg.cabac_dz_div > 0 {
fe.idz = cfg.cabac_dz_div; } let (sy, su, sv) = coded_source(cfg, frame);
let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let num_refs = refs.len();
if is_p && fe.me_wide && !refs.is_empty()
&& global_mc_residual(&sy, fe.cw, fe.mb_h * 16, &refs[0].y) < fe.me_wide_coh
{
fe.me_wide = false;
}
if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
let hr = me_wide_headroom(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
if me_wide_hr_dbg() {
eprintln!("ME_HR qp{qp} headroom={hr:.2}");
}
if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
fe.me_wide = false;
}
}
if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
let (mg, dc) = b2_mgain(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
if me_sadt_dbg() {
eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
}
fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
if mv_smooth_mode() == 1 {
fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
}
let smg = split_mg();
if smg > 0.0 {
fe.do_splits = mg >= smg;
}
}
if is_p && fe.satd_q > 0.0 {
let mut vars: Vec<i64> = (0..fe.mb_h)
.flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
.map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
.collect();
vars.sort_unstable();
let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
fe.satd_var_thresh = vars[idx];
}
let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
apply_mbtree_qpo(&mut aq_qp, qpo); fe.cur_qp = qp;
let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
let mut skip_run = 0u32;
if is_p && mv_cmp_on() {
MVCMP_FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
let mut rdskip_snap = MbState::default();
let mut rdskip_free = 0usize;
let mut rdskip_seen = 0usize;
let mut rdskip_on = false;
let mut greedy_on = fe.greedy_min_free == 0; let rdskip_learn = (fe.mb_w * fe.mb_h / 8).max(64);
let rdskip_min_free = fe.rd_skip_min_free as usize;
drop(_g_prep);
let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
for mb_y in 0..fe.mb_h {
for mb_x in 0..fe.mb_w {
let mb_idx = mb_y * fe.mb_w + mb_x;
fe.qp = aq_qp[mb_idx];
fe.qpc = chroma_qp(aq_qp[mb_idx]);
let mut inter: Option<InterChoice> = None;
let mut coded: Option<BitWriter> = None;
if is_p {
if num_refs > 0 {
let _g_skip = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncSkip);
let _g_smc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
rdskip_seen += 1;
if rdskip_seen >= rdskip_learn {
rdskip_on = rdskip_free * 100 >= rdskip_seen * rdskip_min_free;
greedy_on = fe.greedy_min_free == 0
|| rdskip_free * 100 >= rdskip_seen * fe.greedy_min_free as usize;
}
let mv_skip = fe.skip_mv(mb_x, mb_y);
let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
drop(_g_smc);
let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
let skip_c = if luma_free || !fe.fast {
fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
} else {
[[0u8; 64]; 2]
};
let is_free =
luma_free && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &skip_c);
let skip_sad = if fe.fast {
0
} else {
let (lx, ly) = (mb_x * 16, mb_y * 16);
let mut s = 0u32;
for dy in 0..16 {
let src = &sy[(ly + dy) * fe.cw + lx..][..16];
let p = &skip_y[dy * 16..][..16];
s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
s
};
if is_free {
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
if !fe.fast {
fe.mb_was_skip[mb_idx] = true;
fe.mb_skip_sad[mb_idx] = skip_sad;
}
mb_qpy[mb_idx] = fe.cur_qp; rdskip_free += 1;
if precomp {
bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
}
skip_run += 1;
continue;
}
drop(_g_skip);
let (lx, ly) = (mb_x * 16, mb_y * 16);
let nb = {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
};
let lme = lambda.sqrt();
if fe.fast {
fe.mb_use_satd = fe.satd_q > 0.0
&& mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
let (r16, mv16, cost_inter) =
fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
let cost_intra = if fe.mb_use_satd {
fe.best_i16_satd(&sy, mb_x, mb_y)
} else {
fe.best_i16_sad(&sy, mb_x, mb_y)
} + (lme * fe.tune_intra_penalty) as i64;
inter = if cost_intra < cost_inter {
None } else {
Some((0, vec![(r16, mv16)]))
};
} else {
if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
fe.mb_was_skip[mb_idx] = true;
fe.mb_skip_sad[mb_idx] = skip_sad;
mb_qpy[mb_idx] = fe.cur_qp; if precomp {
bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, rusty_h264_common::deblock::MbKind::Skip);
}
skip_run += 1;
continue;
}
let (r16, mv16, c16) =
fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
let mut best_c = c16;
let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
let split_t = split_t();
if fe.do_splits && c16 > split_gate && (split_t <= 0.0 || (c16 as f64) >= split_t * lme) {
let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
if ct + cb < best_c {
best_c = ct + cb;
pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
}
if cl + cr < best_c {
best_c = cl + cr;
pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
}
if fe.sub8x8 {
let mut c8 = (lme * 4.0) as i64; let mut p8 = Vec::with_capacity(4);
for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
let (r, mv, c) = fe.best_part(
refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
);
c8 += c;
p8.push((r, mv));
}
if c8 < best_c {
best_c = c8;
pick = Some((3u8, p8));
}
}
}
if fe.sp_defer.get() {
if let Some((mode, parts)) = pick.as_mut() {
let regions: &[(usize, usize, usize, usize)] = match mode {
1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
_ => &[(0, 0, 16, 16)],
};
let mut tot = if *mode == 3 { (lme * 4.0) as i64 } else { 0 };
for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
let (r, mv) = parts[i];
let (m2, c2) = fe.refine_part(
refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
);
parts[i] = (r, m2);
tot += c2;
}
best_c = tot;
}
}
if split_harvest::enabled() {
let won = match pick.as_ref().map(|p| p.0) {
Some(0) | None => 0u8,
Some(m) => m,
};
split_harvest::record(c16, best_c, lme, split_gate, won);
}
let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
+ (lme * fe.tune_intra_penalty) as i64;
inter = if c_intra < best_c { None } else { pick };
fe.mb_was_skip[mb_idx] = false;
fe.mb_skip_sad[mb_idx] = skip_sad;
}
if fe.rd_skip && rdskip_on && inter.is_some() {
let skip_cp = fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip);
let ssd_s = fe.pred_ssd(&sy, &su, &sv, mb_x, mb_y, &skip_y, &skip_cp);
debug_assert_eq!(ssd_s, {
let snap = fe.save_mb(mb_x, mb_y);
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
let v = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
fe.load_mb(mb_x, mb_y, &snap);
v
}, "skip prediction SSD must equal the committed-skip reconstruction SSD");
let j_skip = ssd_s as f64 + lambda;
let take_skip = if fe.rd_skip_fast_t > 0.0
&& (ssd_s as f64) <= lambda * fe.rd_skip_fast_t
{
true
} else {
fe.save_mb_into(mb_x, mb_y, &mut rdskip_snap);
let mut scratch = BitWriter::new();
{
let (m, p) = inter.as_ref().unwrap();
fe.encode_inter_mb(
&mut scratch, refs, &sy, &su, &sv, mb_x, mb_y, *m, p,
);
}
let bits_c = scratch.bit_len();
let ssd_c = fe.mb_ssd(&sy, &su, &sv, mb_x, mb_y);
let won = j_skip <= ssd_c as f64 + lambda * bits_c as f64;
if won {
fe.load_mb(mb_x, mb_y, &rdskip_snap); true
} else {
coded = Some(scratch); false
}
};
if take_skip {
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_cp);
if !fe.fast {
fe.mb_was_skip[mb_idx] = true;
fe.mb_skip_sad[mb_idx] = skip_sad;
}
mb_qpy[mb_idx] = fe.cur_qp;
if precomp {
bs_grid[mb_idx] = derive_mb_bs_from(
&fe, mb_x, mb_y,
rusty_h264_common::deblock::MbKind::Skip,
);
}
skip_run += 1;
continue;
}
}
}
w.write_ue(skip_run); skip_run = 0;
}
if mv_force_on() && is_p && inter.is_some() {
let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
let ext = EXT_MV.lock().unwrap();
if let Some(field) = ext.get(fi) {
let w4 = fe.mb_w * 4;
let b0 = (mb_y * 4) * w4 + mb_x * 4;
let uniform = (0..4).all(|r| {
(0..4).all(|c| field.get(b0 + r * w4 + c) == field.get(b0))
});
if uniform {
if let Some(&emv) = field.get(b0) {
inter = Some((0, vec![(0, emv)]));
MVCMP[6].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
}
}
if mv_cmp_on() && is_p {
if let Some((mode, parts)) = inter.as_ref() {
let fi = MVCMP_FRAME.load(std::sync::atomic::Ordering::Relaxed);
let ext = EXT_MV.lock().unwrap();
if let Some(field) = ext.get(fi) {
let bidx = (mb_y * 4) * (fe.mb_w * 4) + mb_x * 4;
if let Some(&emv) = field.get(bidx) {
let (mode, parts) = (*mode, parts.clone());
drop(ext);
let (so, bo) =
fe.trial_inter(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts);
let (se, be) = fe.trial_inter(
refs, &sy, &su, &sv, mb_x, mb_y, 0, &[(0, emv)],
);
let jo = so as f64 + lambda * bo as f64;
let je = se as f64 + lambda * be as f64;
use std::sync::atomic::Ordering::Relaxed;
MVCMP[0].fetch_add(1, Relaxed);
MVCMP[1].fetch_add(bo as u64, Relaxed);
MVCMP[2].fetch_add(be as u64, Relaxed);
MVCMP[3].fetch_add(so.max(0) as u64, Relaxed);
MVCMP[4].fetch_add(se.max(0) as u64, Relaxed);
MVCMP[5].fetch_add((je < jo) as u64, Relaxed);
MVCMP[6].fetch_add((parts[0].1 != emv) as u64, Relaxed);
}
}
}
}
let mb_kind = match &inter {
Some((_, parts)) if parts.len() == 1 => {
rusty_h264_common::deblock::MbKind::InterUniform
}
Some(_) => rusty_h264_common::deblock::MbKind::Inter,
None => rusty_h264_common::deblock::MbKind::Intra,
};
match inter {
Some((mode, parts)) => match coded {
Some(sc) => w.append(&sc),
None => {
fe.encode_inter_mb(w, refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts)
}
},
None => encode_mb(&mut fe, w, mb_x, mb_y, &sy, &su, &sv, is_p),
}
mb_qpy[mb_idx] = fe.cur_qp; if precomp {
bs_grid[mb_idx] = derive_mb_bs_from(&fe, mb_x, mb_y, mb_kind);
}
}
}
debug_assert!(
!precomp || bs_grid.iter().all(|b| *b != rusty_h264_common::deblock::MbBs::UNSET),
"a macroblock loop exit failed to store its boundary strengths"
);
if is_p && skip_run > 0 {
w.write_ue(skip_run); }
w.rbsp_trailing_bits();
drop(_g_loop);
let _g_fin = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncFinal);
let info = rusty_h264_common::deblock::BlockInfo {
inter: &fe.inter_y,
nnz: &fe.nnz_y,
mv: &fe.mv_y,
ref_id: &fe.ref_idx_y,
mv1: &[],
ref_id1: &[],
w4: fe.mb_w * 4,
t8x8: &[],
bs: &bs_grid,
};
drop(_g_fin);
rusty_h264_common::deblock::filter_frame(
&mut fe.rec_y,
&mut fe.rec_u,
&mut fe.rec_v,
fe.mb_w,
fe.mb_h,
&mb_qpy,
0, 0, 0, &info,
);
let w4 = fe.mb_w * 4;
crate::RefFrame {
y: fe.rec_y,
u: fe.rec_u,
v: fe.rec_v,
poc: 0, frame_num: 0, mv: fe.mv_y,
ref_idx: fe.ref_idx_y,
w4,
hpel: std::sync::OnceLock::new(),
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub fn encode_slice_data_b(
w: &mut BitWriter,
cfg: &EncoderConfig,
frame: &YuvFrame,
qp: u8,
poc: i32,
l0: &crate::RefFrame,
l1: &crate::RefFrame,
qpo: &[i32],
) {
let mut fe = FrameEncoder::new(cfg);
fe.qp = qp;
fe.qpc = chroma_qp(qp);
fe.cur_qp = qp;
if cfg.cabac_dz_div > 0 {
fe.idz = cfg.cabac_dz_div; } fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
let (sy, su, sv) = coded_source(cfg, frame);
let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let lme = lambda.sqrt();
let refs = std::slice::from_ref(l0); if fe.satd_q > 0.0 {
let mut vars: Vec<i64> = (0..fe.mb_h)
.flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
.map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
.collect();
vars.sort_unstable();
let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
fe.satd_var_thresh = vars[idx];
}
let mut skip_run = 0u32; for mb_y in 0..fe.mb_h {
for mb_x in 0..fe.mb_w {
let (lx, ly) = (mb_x * 16, mb_y * 16);
let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
fe.mb_use_satd =
fe.satd_q > 0.0 && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
if fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
&& fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc)
{
fe.commit_direct_motion(mb_x, mb_y, &dmotion);
skip_run += 1;
continue;
}
let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
+ mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
let j_bi = d_bi + (lme * r_bi as f64) as i64;
let (mut dir, mut best) = (0u8, d_direct);
if j0 < best { dir = 1; best = j0; }
if j1 < best { dir = 2; best = j1; }
if j_bi < best { dir = 3; best = j_bi; }
let _ = best;
w.write_ue(skip_run); skip_run = 0;
let bspec = BInter { dir, l1, mv0, mv1 };
fe.encode_inter_mb_v1_b(w, refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec));
}
}
if skip_run > 0 {
w.write_ue(skip_run); }
w.rbsp_trailing_bits();
}
#[inline(always)]
fn mvd_bits(d: i32) -> u32 {
let codenum = if d > 0 { (2 * d - 1) as u32 } else { (-2 * d) as u32 };
1 + 2 * (31 - (codenum + 1).leading_zeros())
}
fn write_ref_idx(w: &mut BitWriter, refi: i32, num_refs: usize) {
if num_refs == 2 {
w.write_bit(refi == 0); } else {
w.write_ue(refi as u32);
}
}
fn ref_bits(r: usize, num_refs: usize) -> u32 {
if num_refs <= 1 {
0
} else if num_refs == 2 {
1
} else {
let mut n = r as u32 + 1;
let mut len = 1;
while n > 1 {
n >>= 1;
len += 2;
}
len
}
}
fn residual(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[i32; 16]) -> [i32; 16] {
let mut r = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
r[dy * 4 + dx] = src[(y0 + dy) * stride + (x0 + dx)] as i32 - pred[dy * 4 + dx];
}
}
r
}
fn store(plane: &mut [u8], stride: usize, x0: usize, y0: usize, s: &[u8; 16]) {
for dy in 0..4 {
for dx in 0..4 {
plane[(y0 + dy) * stride + (x0 + dx)] = s[dy * 4 + dx];
}
}
}
fn pred_block(pred: &[u8; 256], bx: usize, by: usize) -> [i32; 16] {
let mut p = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
p[dy * 4 + dx] = pred[(by * 4 + dy) * 16 + (bx * 4 + dx)] as i32;
}
}
p
}
#[inline]
pub(crate) fn satd_px(src: &[u8], ss: usize, pred: &[u8], ps: usize, w: usize, h: usize) -> i64 {
#[cfg(accel)]
{
let asm = match (w, h) {
(16, 16) => Some(rusty_h264_accel::satd_16x16(src, ss, pred, ps)),
(16, 8) => Some(rusty_h264_accel::satd_16x8(src, ss, pred, ps)),
(8, 16) => Some(rusty_h264_accel::satd_8x16(src, ss, pred, ps)),
(8, 8) => Some(rusty_h264_accel::satd_8x8(src, ss, pred, ps)),
(4, 4) => Some(rusty_h264_accel::satd_4x4(src, ss, pred, ps)),
_ => None,
};
if let Some(v) = asm {
return 2 * v as i64;
}
}
let (nbx, nby) = (w / 4, h / 4);
let mut blocks = [[0i32; 16]; 16];
let mut bi = 0;
for by in 0..nby {
for bx in 0..nbx {
let blk = &mut blocks[bi];
for dy in 0..4 {
for dx in 0..4 {
blk[dy * 4 + dx] =
src[(by * 4 + dy) * ss + bx * 4 + dx] as i32 - pred[(by * 4 + dy) * ps + bx * 4 + dx] as i32;
}
}
bi += 1;
}
}
satd_4x4_sum(&blocks[..nbx * nby])
}
#[inline]
fn sad_strided(src: &[u8], ss: usize, r: &[u8], rs: usize, w: usize, h: usize) -> i64 {
#[cfg(accel)]
{
match (w, h) {
(16, 16) => return rusty_h264_accel::sad_16x16(src, ss, r, rs) as i64,
(16, 8) => return rusty_h264_accel::sad_16x8(src, ss, r, rs) as i64,
(8, 16) => return rusty_h264_accel::sad_8x16(src, ss, r, rs) as i64,
_ => {}
}
}
let mut sad = 0u32;
for dy in 0..h {
let a = &src[dy * ss..][..w];
let b = &r[dy * rs..][..w];
sad += a.iter().zip(b).map(|(&x, &y)| x.abs_diff(y) as u32).sum::<u32>();
}
sad as i64
}
#[inline]
fn sad_avg_strided(src: &[u8], ss: usize, a: &[u8], b: &[u8], rs: usize, w: usize, h: usize) -> i64 {
let mut sad = 0u32;
for dy in 0..h {
let s = &src[dy * ss..][..w];
let pa = &a[dy * rs..][..w];
let pb = &b[dy * rs..][..w];
for i in 0..w {
let p = ((pa[i] as u16 + pb[i] as u16 + 1) >> 1) as u8;
sad += s[i].abs_diff(p) as u32;
}
}
sad as i64
}
fn satd_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
satd_px(&src[ly * stride + lx..], stride, pred, 16, 16, 16)
}
fn sad_16x16(src: &[u8], stride: usize, lx: usize, ly: usize, pred: &[u8; 256]) -> i64 {
let mut sad = 0u32;
for dy in 0..16 {
let s = &src[(ly + dy) * stride + lx..][..16];
let p = &pred[dy * 16..][..16];
sad += s.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
sad as i64
}
fn satd_8x8(src: &[u8], stride: usize, x0: usize, y0: usize, pred: &[u8; 64]) -> i64 {
satd_px(&src[y0 * stride + x0..], stride, pred, 8, 8, 8)
}
fn satd_4x4(src: &[u8], stride: usize, px: usize, py: usize, pred: &[u8; 16]) -> i64 {
satd_px(&src[py * stride + px..], stride, pred, 4, 4, 4)
}
fn i4_mode_available(mode: u8, top: bool, left: bool) -> bool {
match mode {
0 | 3 | 7 => top, 1 | 8 => left, 2 => true, _ => top && left, }
}
struct I4Plan {
modes: [u8; 16], q: [[i32; 16]; 16], cbp_luma: u32, nonzero: i64, }
struct MbPlan {
use_i4: bool,
i16_mode: I16Mode,
i16_cbp15: bool,
i16_dc_levels: [i32; 16],
i16_q: [[i32; 16]; 16],
i4: Option<I4Plan>,
i8: Option<I8Plan>,
chroma_mode: u8,
cbp_chroma: u32,
c_dc_levels: [[i32; 4]; 2],
c_q_blocks: [[[i32; 16]; 4]; 2],
}
struct InterPlan {
mvds: [(i32, i32); 4], plan_refs: [i32; 4], n_mvd: usize,
cbp: u32,
q_blocks: [[i32; 16]; 16], c_dc_levels: [[i32; 4]; 2],
c_q: [[[i32; 16]; 4]; 2],
t8x8: bool, q8: [[i32; 64]; 4], }
fn gather_i4(
fe: &FrameEncoder,
px: usize,
py: usize,
avail_top: bool,
avail_left: bool,
bx: usize,
by: usize,
) -> ([u8; 8], [u8; 4], u8) {
let (cw, w4) = (fe.cw, fe.mb_w * 4);
let mut top = [0u8; 8];
let mut left = [0u8; 4];
let mut corner = 0;
if avail_top {
for i in 0..4 {
top[i] = fe.rec_y[(py - 1) * cw + px + i];
}
let tr_avail = bx + 1 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 1)];
for i in 0..4 {
top[4 + i] = if tr_avail {
fe.rec_y[(py - 1) * cw + px + 4 + i]
} else {
top[3]
};
}
}
if avail_left {
for i in 0..4 {
left[i] = fe.rec_y[(py + i) * cw + px - 1];
}
}
if avail_top && avail_left {
corner = fe.rec_y[(py - 1) * cw + px - 1];
}
(top, left, corner)
}
#[inline]
fn modes_at(fe: &FrameEncoder, modes: &[u8; 16], lbx: usize, lby: usize, dx: isize, dy: isize, bx: usize, by: usize) -> u8 {
let (nx, ny) = (lbx as isize + dx, lby as isize + dy);
if (0..4).contains(&nx) && (0..4).contains(&ny) {
modes[ny as usize * 4 + nx as usize]
} else {
let w4 = fe.mb_w * 4;
let gx = (bx as isize + dx) as usize;
let gy = (by as isize + dy) as usize;
fe.modes_y[gy * w4 + gx]
}
}
fn plan_i4x4(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I4Plan {
let w4 = fe.mb_w * 4;
let mut modes = [2u8; 16];
let mut q = [[0i32; 16]; 16];
let mut cbp_luma = 0u32;
let mut nonzero = 0i64;
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let (px, py) = (bx * 4, by * 4);
let avail_top = by > 0;
let avail_left = bx > 0;
let (top, left, corner) = gather_i4(fe, px, py, avail_top, avail_left, bx, by);
let mut best_m = 2u8;
let mut best_cost = i64::MAX;
if fe.fast && fast_intra_enabled() {
let lm = if bx > 0 { modes_at(fe, &modes, lbx, lby, -1, 0, bx, by) } else { 2 };
let tm = if by > 0 { modes_at(fe, &modes, lbx, lby, 0, -1, bx, by) } else { 2 };
let mpm = lm.min(tm);
let mut cands = [mpm, 2u8, 0, 1];
for i in 1..4 {
for j in 0..i {
if cands[i] == cands[j] {
cands[i] = 255;
}
}
}
for &m in cands.iter() {
if m == 255 || !i4_mode_available(m, avail_top, avail_left) {
continue;
}
let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
let cost = satd_4x4(sy, fe.cw, px, py, &pred);
if cost < best_cost {
best_cost = cost;
best_m = m;
}
}
} else {
for m in 0..9u8 {
if !i4_mode_available(m, avail_top, avail_left) {
continue;
}
let pred = intra4x4_pred(m, avail_top, avail_left, &top, &left, corner);
let cost = satd_4x4(sy, fe.cw, px, py, &pred);
if cost < best_cost {
best_cost = cost;
best_m = m;
}
}
}
let pred = intra4x4_pred(best_m, avail_top, avail_left, &top, &left, corner);
let mut predb = [0i32; 16];
for i in 0..16 {
predb[i] = pred[i] as i32;
}
let res = residual(sy, fe.cw, px, py, &predb);
let qb = rdoq(&forward_core(&res), qp, fe.idz, fe.rdoq_strength, 0); let s = reconstruct_4x4(&dequantize(&qb, qp), &predb);
store(&mut fe.rec_y, fe.cw, px, py, &s);
fe.coded_y[by * w4 + bx] = true;
let nz = qb.iter().filter(|&&v| v != 0).count();
if nz > 0 {
cbp_luma |= 1 << ((lby / 2) * 2 + (lbx / 2));
}
nonzero += nz as i64;
modes[lby * 4 + lbx] = best_m;
q[lby * 4 + lbx] = qb;
}
I4Plan {
modes,
q,
cbp_luma,
nonzero,
}
}
struct I8Plan {
modes: [u8; 4], q: [[i32; 64]; 4], cbp_luma: u32, nonzero: i64, }
const ZIGZAG_8X8: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
];
#[inline]
fn scan_8x8_fwd(raster: &[i32; 64]) -> [i32; 64] {
std::array::from_fn(|i| raster[ZIGZAG_8X8[i]])
}
fn gather_i8_enc(
fe: &FrameEncoder,
px: usize,
py: usize,
avail_top: bool,
avail_left: bool,
bx: usize,
by: usize,
) -> ([u8; 16], [u8; 8], u8, bool) {
let (cw, w4) = (fe.cw, fe.mb_w * 4);
let mut top = [0u8; 16];
let mut left = [0u8; 8];
let mut corner = 0;
if avail_top {
for i in 0..8 {
top[i] = fe.rec_y[(py - 1) * cw + px + i];
}
let tr_avail = bx + 2 < w4 && fe.coded_y[(by - 1) * w4 + (bx + 2)];
for i in 0..8 {
top[8 + i] = if tr_avail {
fe.rec_y[(py - 1) * cw + px + 8 + i]
} else {
top[7]
};
}
}
if avail_left {
for i in 0..8 {
left[i] = fe.rec_y[(py + i) * cw + px - 1];
}
}
let avail_corner = avail_top && avail_left;
if avail_corner {
corner = fe.rec_y[(py - 1) * cw + px - 1];
}
(top, left, corner, avail_corner)
}
fn plan_i8x8(fe: &mut FrameEncoder, sy: &[u8], mb_x: usize, mb_y: usize, qp: u8) -> I8Plan {
let w4 = fe.mb_w * 4;
let mut modes = [2u8; 4];
let mut q = [[0i32; 64]; 4];
let mut cbp_luma = 0u32;
let mut nonzero = 0i64;
let weight = [16i32; 64];
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2); let avail_top = b8y > 0 || mb_y > 0;
let avail_left = b8x > 0 || mb_x > 0;
let (top, left, corner, avail_corner) =
gather_i8_enc(fe, px, py, avail_top, avail_left, bx, by);
let predicted = predict_i4_mode(fe, bx, by);
let mut best_m = 2u8;
let mut best_cost = i64::MAX;
for m in 0..9u8 {
if !i4_mode_available(m, avail_top, avail_left) {
continue;
}
let pred = intra8x8_pred(m, avail_top, avail_left, avail_corner, &top, &left, corner);
let mut cost = satd_8x8(sy, fe.cw, px, py, &pred);
if m != predicted {
cost += 4 * fe.qp as i64; }
if cost < best_cost {
best_cost = cost;
best_m = m;
}
}
modes[b8] = best_m;
let pred = intra8x8_pred(best_m, avail_top, avail_left, avail_corner, &top, &left, corner);
let mut res = [0i32; 64];
for dy in 0..8 {
for dx in 0..8 {
res[dy * 8 + dx] =
sy[(py + dy) * fe.cw + (px + dx)] as i32 - pred[dy * 8 + dx] as i32;
}
}
let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, fe.idz);
let nz = levels.iter().filter(|&&v| v != 0).count();
if nz > 0 {
cbp_luma |= 1 << b8;
}
nonzero += nz as i64;
q[b8] = levels;
let res_r = inverse_quant_8x8(&levels, qp, &weight);
let predb: [i32; 64] = std::array::from_fn(|i| pred[i] as i32);
let recon = add_residual_8x8(&res_r, &predb);
for dy in 0..8 {
for dx in 0..8 {
fe.rec_y[(py + dy) * fe.cw + (px + dx)] = recon[dy * 8 + dx];
}
}
for sry in 0..2 {
for srx in 0..2 {
fe.modes_y[(by + sry) * w4 + (bx + srx)] = best_m;
fe.coded_y[(by + sry) * w4 + (bx + srx)] = true;
}
}
}
I8Plan {
modes,
q,
cbp_luma,
nonzero,
}
}
#[allow(clippy::too_many_arguments)]
fn plan_inter8_luma(
sy: &[u8],
cw: usize,
mb_x: usize,
mb_y: usize,
pred_y: &[u8; 256],
qp: u8,
) -> ([[i32; 64]; 4], u32, f64, [u8; 256], i64) {
let weight = [16i32; 64];
let mut q8 = [[0i32; 64]; 4];
let mut cbp = 0u32;
let mut rate = 0f64;
let mut rec = [0u8; 256];
let mut ssd = 0i64;
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let mut res = [0i32; 64];
for dy in 0..8 {
for dx in 0..8 {
let sx = mb_x * 16 + b8x * 8 + dx;
let syy = mb_y * 16 + b8y * 8 + dy;
let p = pred_y[(b8y * 8 + dy) * 16 + (b8x * 8 + dx)] as i32;
res[dy * 8 + dx] = sy[syy * cw + sx] as i32 - p;
}
}
let levels = quantize_8x8(&forward_core_8x8(&res), qp, &weight, 6);
let mut nz = false;
for &l in &levels {
if l != 0 {
nz = true;
rate += rdoq_rate((l as i64).abs());
}
}
if nz {
cbp |= 1 << b8;
}
q8[b8] = levels;
let res_r = inverse_quant_8x8(&levels, qp, &weight);
let predb: [i32; 64] =
std::array::from_fn(|i| pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32);
let recon = add_residual_8x8(&res_r, &predb);
for dy in 0..8 {
for dx in 0..8 {
let ri = (b8y * 8 + dy) * 16 + (b8x * 8 + dx);
rec[ri] = recon[dy * 8 + dx];
let sx = mb_x * 16 + b8x * 8 + dx;
let syy = mb_y * 16 + b8y * 8 + dy;
let d = recon[dy * 8 + dx] as i64 - sy[syy * cw + sx] as i64;
ssd += d * d;
}
}
}
(q8, cbp, rate, rec, ssd)
}
#[inline]
fn i16_pred(
fe: &FrameEncoder,
mode: I16Mode,
avail_top: bool,
avail_left: bool,
top: &[u8; 16],
left: &[u8; 16],
corner: u8,
lx: usize,
ly: usize,
) -> [u8; 256] {
#[cfg(accel)]
if avail_top && avail_left {
let mode_n = match mode {
I16Mode::Vertical => 0,
I16Mode::Horizontal => 1,
I16Mode::Dc => 2,
I16Mode::Plane => 3,
};
let mut p = AlignedMb([0; 256]);
rusty_h264_accel::i16x16_luma_pred(mode_n, &mut p.0, &fe.rec_y[..], ly * fe.cw + lx, fe.cw);
return p.0;
}
let _ = (fe, lx, ly);
luma16x16_pred(mode, avail_top, avail_left, top, left, corner)
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn chroma_pred(
fe: &FrameEncoder,
mode: u8,
avail_top: bool,
avail_left: bool,
c: usize,
top: &[u8; 8],
left: &[u8; 8],
corner: u8,
cx: usize,
cy: usize,
) -> [u8; 64] {
#[cfg(accel)]
if avail_top && avail_left && (mode == 2 || mode == 3) {
let plane = if c == 0 { &fe.rec_u } else { &fe.rec_v };
let mut p = AlignedMb([0; 256]);
rusty_h264_accel::chroma8x8_pred(mode, &mut p.0[..64], &plane[..], cy * fe.ccw + cx, fe.ccw);
let mut out = [0u8; 64];
out.copy_from_slice(&p.0[..64]);
return out;
}
let _ = (fe, c, cx, cy);
chroma8x8_pred(mode, avail_top, avail_left, top, left, corner)
}
fn predict_i4_mode(fe: &FrameEncoder, bx: usize, by: usize) -> u8 {
if bx == 0 || by == 0 {
return 2;
}
let w4 = fe.mb_w * 4;
fe.modes_y[by * w4 + (bx - 1)].min(fe.modes_y[(by - 1) * w4 + bx])
}
#[allow(clippy::too_many_arguments)]
const RDOQ_ZZ: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
#[inline]
fn rdoq_rate(level: i64) -> f64 {
if level == 0 {
1.0
} else if level == 1 {
3.0 } else {
3.0 + (level - 1).min(13) as f64
}
}
fn rdoq(coeffs: &[i32; 16], qp: u8, dz_div: i64, strength: f64, first: usize) -> [i32; 16] {
let mut q = quantize(coeffs, qp, dz_div);
if strength <= 0.0 {
return q;
}
let lambda = strength * 2f64.powf((qp as f64 - 12.0) / 3.0);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
const POS: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
let dist = |p: usize, level: i64| -> f64 {
let e = coeffs[p].unsigned_abs() as f64 - level as f64 * (65536.0 / mf[POS[p]] as f64);
e * e
};
for i in first..16 {
let p = RDOQ_ZZ[i];
let m = q[p].unsigned_abs() as i64;
if m == 0 {
continue;
}
let j_keep = dist(p, m) + lambda * rdoq_rate(m);
let j_down = dist(p, m - 1) + lambda * rdoq_rate(m - 1);
if j_down < j_keep {
let nl = (m - 1) as i32;
q[p] = if q[p] < 0 { -nl } else { nl };
}
}
loop {
let Some(li) = (first..16).rev().find(|&i| q[RDOQ_ZZ[i]] != 0) else {
break;
};
let p = RDOQ_ZZ[li];
let m = q[p].unsigned_abs() as i64;
let prev = (first..li).rev().find(|&i| q[RDOQ_ZZ[i]] != 0);
let base = prev.map_or(first, |j| j + 1);
let bits = rdoq_rate(m) + 1.0 + (li - base) as f64; let d_add = dist(p, 0) - dist(p, m);
if d_add < lambda * bits {
q[p] = 0;
} else {
break;
}
}
q
}
fn plan_mb(
fe: &mut FrameEncoder,
mb_x: usize,
mb_y: usize,
sy: &[u8],
su: &[u8],
sv: &[u8],
) -> MbPlan {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncIntraCode);
let qp = fe.qp;
let qpc = fe.qpc;
let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let (lx, ly) = (mb_x * 16, mb_y * 16);
let avail_top = mb_y > 0;
let avail_left = mb_x > 0;
let mut top = [0u8; 16];
let mut left = [0u8; 16];
if avail_top {
for i in 0..16 {
top[i] = fe.rec_y[(ly - 1) * fe.cw + lx + i];
}
}
if avail_left {
for i in 0..16 {
left[i] = fe.rec_y[(ly + i) * fe.cw + lx - 1];
}
}
let corner = if avail_top && avail_left {
fe.rec_y[(ly - 1) * fe.cw + lx - 1]
} else {
0
};
let w4 = fe.mb_w * 4;
let mut i16_mode = I16Mode::Dc;
let mut best_pred = i16_pred(fe, I16Mode::Dc, avail_top, avail_left, &top, &left, corner, lx, ly);
let mut best_cost = satd_16x16(sy, fe.cw, lx, ly, &best_pred);
for mode in [I16Mode::Vertical, I16Mode::Horizontal, I16Mode::Plane] {
if !mode.available(avail_top, avail_left) {
continue;
}
let pred = i16_pred(fe, mode, avail_top, avail_left, &top, &left, corner, lx, ly);
let cost = satd_16x16(sy, fe.cw, lx, ly, &pred);
if cost < best_cost {
best_cost = cost;
i16_mode = mode;
best_pred = pred;
}
}
let mut dc4x4 = [0i32; 16];
let mut i16_q = [[0i32; 16]; 16];
#[cfg(accel)]
let (i16_dc_levels, _i16_recon_dc, recon16) = {
#[repr(align(16))]
struct A([i16; 256]);
let mut dct = A([0i16; 256]);
let base = ly * fe.cw + lx;
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
rusty_h264_accel::dct_four_t4(
&mut dct.0[qi * 64..qi * 64 + 64],
&sy[base + qy * fe.cw + qx..],
fe.cw,
&best_pred[qy * 16 + qx..],
16,
);
}
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
dc4x4[lby * 4 + lbx] = dct.0[blk * 16] as i32;
}
if fe.rdoq_strength > 0.0 {
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let coeffs: [i32; 16] = std::array::from_fn(|i| dct.0[blk * 16 + i] as i32);
let mut q = rdoq(&coeffs, qp, fe.idz, fe.rdoq_strength, 1);
q[0] = 0;
i16_q[lby * 4 + lbx] = q;
}
} else {
let ff = rusty_h264_common::transform::quant_dz_ff(qp, fe.idz);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qp as usize];
for qi in 0..4 {
rusty_h264_accel::quant_four_4x4(&mut dct.0[qi * 64..qi * 64 + 64], &ff, mf);
}
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let q = &mut i16_q[lby * 4 + lbx];
q[0] = 0;
for i in 1..16 {
q[i] = dct.0[blk * 16 + i] as i32;
}
}
}
let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
let mut recon16 = [0u8; 256];
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let mut deq = dequantize(&i16_q[lby * 4 + lbx], qp);
deq[0] = i16_recon_dc[lby * 4 + lbx];
for i in 0..16 {
dct.0[blk * 16 + i] = deq[i] as i16;
}
}
for (qi, &(qx, qy)) in [(0usize, 0usize), (8, 0), (0, 8), (8, 8)].iter().enumerate() {
rusty_h264_accel::idct_four_t4_rec(
&mut recon16[qy * 16 + qx..],
16,
&best_pred[qy * 16 + qx..],
16,
&dct.0[qi * 64..qi * 64 + 64],
);
}
(i16_dc_levels, i16_recon_dc, recon16)
};
#[cfg(not(accel))]
let (i16_dc_levels, _i16_recon_dc, recon16) = {
let mut res_blocks = [[0i32; 16]; 16];
for by in 0..4 {
for bx in 0..4 {
let predb = pred_block(&best_pred, bx, by);
res_blocks[by * 4 + bx] = residual(sy, fe.cw, lx + bx * 4, ly + by * 4, &predb);
}
}
let mut coeffs = [[0i32; 16]; 16];
forward_dct_blocks(&res_blocks, &mut coeffs);
for i in 0..16 {
dc4x4[i] = coeffs[i][0];
let mut q = rdoq(&coeffs[i], qp, fe.idz, fe.rdoq_strength, 1);
q[0] = 0;
i16_q[i] = q;
}
let i16_dc_levels = forward_quant_luma_dc(&dc4x4, qp, true);
let i16_recon_dc = inverse_quant_luma_dc(&i16_dc_levels, qp);
let mut recon16 = [0u8; 256];
let mut deq_blocks = [[0i32; 16]; 16];
for i in 0..16 {
deq_blocks[i] = dequantize(&i16_q[i], qp);
deq_blocks[i][0] = i16_recon_dc[i];
}
let mut idct = [[0i32; 16]; 16];
inverse_dct_blocks(&deq_blocks, &mut idct);
for by in 0..4 {
for bx in 0..4 {
let s = add_residual_4x4(&idct[by * 4 + bx], &pred_block(&best_pred, bx, by));
for dy in 0..4 {
for dx in 0..4 {
recon16[(by * 4 + dy) * 16 + (bx * 4 + dx)] = s[dy * 4 + dx];
}
}
}
}
(i16_dc_levels, i16_recon_dc, recon16)
};
let i16_cbp15 = i16_q.iter().any(|b| b[1..].iter().any(|&c| c != 0));
let i16_dc_nz = i16_dc_levels.iter().filter(|&&v| v != 0).count() as i64;
let i16_ac_nz: i64 = i16_q
.iter()
.map(|b| b[1..].iter().filter(|&&v| v != 0).count() as i64)
.sum();
let i16_rate = i16_dc_nz + i16_ac_nz + if i16_cbp15 { 16 } else { 0 };
let mut ssd16 = 0i64;
for dy in 0..16 {
for dx in 0..16 {
let d = recon16[dy * 16 + dx] as i64 - sy[(ly + dy) * fe.cw + (lx + dx)] as i64;
ssd16 += d * d;
}
}
let (cx, cy) = (mb_x * 8, mb_y * 8);
let mut ntop = [[0u8; 8]; 2];
let mut nleft = [[0u8; 8]; 2];
let mut ncorner = [0u8; 2];
for c in 0..2 {
let rec_c = if c == 0 { &fe.rec_u } else { &fe.rec_v };
if avail_top {
for i in 0..8 {
ntop[c][i] = rec_c[(cy - 1) * fe.ccw + cx + i];
}
}
if avail_left {
for i in 0..8 {
nleft[c][i] = rec_c[(cy + i) * fe.ccw + cx - 1];
}
}
if avail_top && avail_left {
ncorner[c] = rec_c[(cy - 1) * fe.ccw + cx - 1];
}
}
let mut chroma_mode = 0u8;
let mut best_c_cost = i64::MAX;
for m in 0..4u8 {
if !chroma_mode_available(m, avail_top, avail_left) {
continue;
}
let mut cost = 0i64;
for c in 0..2 {
let src = if c == 0 { su } else { sv };
let pred8 = chroma_pred(fe, m, avail_top, avail_left, c, &ntop[c], &nleft[c], ncorner[c], cx, cy);
cost += satd_8x8(src, fe.ccw, cx, cy, &pred8);
}
if cost < best_c_cost {
best_c_cost = cost;
chroma_mode = m;
}
}
let mut c_dc_levels = [[0i32; 4]; 2];
let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
let mut any_chroma_ac = false;
let mut any_chroma_dc = false;
for c in 0..2 {
let src = if c == 0 { su } else { sv };
let pred8 =
chroma_pred(fe, chroma_mode, avail_top, avail_left, c, &ntop[c], &nleft[c], ncorner[c], cx, cy);
let pblk = |bx: usize, by: usize| -> [i32; 16] {
let mut predb = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
predb[dy * 4 + dx] = pred8[(by * 4 + dy) * 8 + (bx * 4 + dx)] as i32;
}
}
predb
};
let mut dc2x2 = [0i32; 4];
let mut qbs = [[0i32; 16]; 4];
#[cfg(accel)]
let recon_dc = {
#[repr(align(16))]
struct A([i16; 64]);
let mut d = A([0i16; 64]);
rusty_h264_accel::dct_four_t4(&mut d.0, &src[cy * fe.ccw + cx..], fe.ccw, &pred8, 8);
for i in 0..4 {
dc2x2[i] = d.0[i * 16] as i32;
}
if fe.rdoq_strength > 0.0 {
for i in 0..4 {
let coeffs: [i32; 16] = std::array::from_fn(|j| d.0[i * 16 + j] as i32);
let mut q = rdoq(&coeffs, qpc, fe.idz, fe.rdoq_strength, 1);
q[0] = 0;
if q[1..].iter().any(|&v| v != 0) {
any_chroma_ac = true;
}
qbs[i] = q;
}
} else {
let ff = rusty_h264_common::transform::quant_dz_ff(qpc, fe.idz);
let mf = &rusty_h264_common::transform::QUANT_MF_OH[qpc as usize];
rusty_h264_accel::quant_four_4x4(&mut d.0, &ff, mf);
for i in 0..4 {
let q = &mut qbs[i];
q[0] = 0;
for j in 1..16 {
let v = d.0[i * 16 + j] as i32;
q[j] = v;
if v != 0 {
any_chroma_ac = true;
}
}
}
}
let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
if dl.iter().any(|&v| v != 0) {
any_chroma_dc = true;
}
let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
for i in 0..4 {
let deq = dequantize(&qbs[i], qpc);
for j in 0..16 {
d.0[i * 16 + j] = deq[j] as i16;
}
d.0[i * 16] = recon_dc[i] as i16;
}
let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
rusty_h264_accel::idct_four_t4_rec(&mut plane[cy * fe.ccw + cx..], fe.ccw, &pred8, 8, &d.0);
c_dc_levels[c] = dl;
recon_dc
};
#[cfg(not(accel))]
let recon_dc = {
let mut res_blocks = [[0i32; 16]; 4];
for by in 0..2 {
for bx in 0..2 {
res_blocks[by * 2 + bx] =
residual(src, fe.ccw, cx + bx * 4, cy + by * 4, &pblk(bx, by));
}
}
let mut coeffs = [[0i32; 16]; 4];
forward_dct_blocks(&res_blocks, &mut coeffs);
for i in 0..4 {
dc2x2[i] = coeffs[i][0];
let mut q = rdoq(&coeffs[i], qpc, fe.idz, fe.rdoq_strength, 1);
q[0] = 0;
qbs[i] = q;
if q[1..].iter().any(|&v| v != 0) {
any_chroma_ac = true;
}
}
let dl = forward_quant_chroma_dc(&dc2x2, qpc, true);
if dl.iter().any(|&v| v != 0) {
any_chroma_dc = true;
}
let recon_dc = inverse_quant_chroma_dc(&dl, qpc);
let mut deq_blocks = [[0i32; 16]; 4];
for i in 0..4 {
deq_blocks[i] = dequantize(&qbs[i], qpc);
deq_blocks[i][0] = recon_dc[i];
}
let mut idct = [[0i32; 16]; 4];
inverse_dct_blocks(&deq_blocks, &mut idct);
let plane = if c == 0 { &mut fe.rec_u } else { &mut fe.rec_v };
for by in 0..2 {
for bx in 0..2 {
let s = add_residual_4x4(&idct[by * 2 + bx], &pblk(bx, by));
store(plane, fe.ccw, cx + bx * 4, cy + by * 4, &s);
}
}
c_dc_levels[c] = dl;
recon_dc
};
let _ = recon_dc;
c_q_blocks[c] = qbs;
}
let cbp_chroma: u32 = if any_chroma_ac {
2
} else if any_chroma_dc {
1
} else {
0
};
let base = ly * fe.cw + lx;
let i4 = if i16_rate > 2 {
Some(plan_i4x4(fe, sy, mb_x, mb_y, qp))
} else {
None
};
let (j4, i4_recon) = match &i4 {
Some(p) => {
let mut ssd = 0i64;
let mut rec = [0u8; 256];
for i in 0..256 {
let v = fe.rec_y[base + (i / 16) * fe.cw + i % 16];
rec[i] = v;
let d = v as i64 - sy[base + (i / 16) * fe.cw + i % 16] as i64;
ssd += d * d;
}
(ssd as f64 + lambda * (p.nonzero + 16) as f64, Some(rec))
}
None => (f64::INFINITY, None),
};
let i8 = if fe.transform_8x8 {
Some(plan_i8x8(fe, sy, mb_x, mb_y, qp))
} else {
None
};
let j8 = match &i8 {
Some(p) => {
let mut ssd = 0i64;
for i in 0..256 {
let d = fe.rec_y[base + (i / 16) * fe.cw + i % 16] as i64
- sy[base + (i / 16) * fe.cw + i % 16] as i64;
ssd += d * d;
}
ssd as f64 + lambda * (p.nonzero + 16) as f64
}
None => f64::INFINITY,
};
let j16 = ssd16 as f64 + lambda * i16_rate as f64;
let (use_i4, i4, i8) = if i8.is_some() && j8 <= j4 && j8 <= j16 {
(true, None, i8)
} else if i4.is_some() && j4 < j16 {
let rec = i4_recon.unwrap();
for i in 0..256 {
fe.rec_y[base + (i / 16) * fe.cw + i % 16] = rec[i];
}
let modes = i4.as_ref().unwrap().modes;
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = modes[lby * 4 + lbx];
}
(true, i4, None)
} else {
for by in 0..4 {
for bx in 0..4 {
for dy in 0..4 {
for dx in 0..4 {
fe.rec_y[(ly + by * 4 + dy) * fe.cw + (lx + bx * 4 + dx)] =
recon16[(by * 4 + dy) * 16 + (bx * 4 + dx)];
}
}
}
}
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.modes_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 2;
}
(false, None, None)
};
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.coded_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = true;
}
MbPlan {
use_i4,
i16_mode,
i16_cbp15,
i16_dc_levels,
i16_q,
i4,
i8,
chroma_mode,
cbp_chroma,
c_dc_levels,
c_q_blocks,
}
}
fn encode_mb(
fe: &mut FrameEncoder,
w: &mut BitWriter,
mb_x: usize,
mb_y: usize,
sy: &[u8],
su: &[u8],
sv: &[u8],
is_p: bool,
) {
let plan = plan_mb(fe, mb_x, mb_y, sy, su, sv);
let mb_type_offset = if is_p { 5 } else { 0 };
let w4 = fe.mb_w * 4;
let cbp_chroma = plan.cbp_chroma;
if let Some(i8) = plan.i8.as_ref().filter(|_| plan.use_i4) {
let cbp = i8.cbp_luma | (cbp_chroma << 4);
w.write_ue(mb_type_offset); w.write_bit(true); for b8 in 0..4usize {
let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
let predicted = predict_i4_mode(fe, bx, by);
let actual = i8.modes[b8];
if actual == predicted {
w.write_bit(true);
} else {
w.write_bit(false);
let rem = if actual < predicted { actual } else { actual - 1 };
w.write_bits(rem as u32, 3);
}
}
w.write_ue(plan.chroma_mode as u32); write_cbp_intra(w, cbp);
if cbp != 0 {
w.write_se(fe.qp_delta());
}
fe.nnz_cache_load(mb_x, mb_y);
for b8 in 0..4usize {
let (b8x, b8y) = (b8 % 2, b8 / 2);
let scan8 = scan_8x8_fwd(&i8.q[b8]);
for sub in 0..4usize {
let (cx, cy) = (b8x * 2 + sub % 2, b8y * 2 + sub / 2);
let (bx, by) = (mb_x * 4 + cx, mb_y * 4 + cy);
let total = if i8.cbp_luma & (1 << b8) != 0 {
let nc = fe.nc_pred(cx, cy);
let blk: [i32; 16] = std::array::from_fn(|k| scan8[4 * k + sub]);
encode_residual_block(w, &blk, 16, nc) as u8
} else {
0
};
fe.nnz_cache_set(cx, cy, total);
fe.nnz_y[by * w4 + bx] = total;
}
}
} else if plan.use_i4 {
let i4 = plan.i4.as_ref().unwrap();
let cbp = i4.cbp_luma | (cbp_chroma << 4);
w.write_ue(mb_type_offset); if fe.transform_8x8 {
w.write_bit(false); }
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let predicted = predict_i4_mode(fe, bx, by);
let actual = i4.modes[lby * 4 + lbx];
if actual == predicted {
w.write_bit(true);
} else {
w.write_bit(false);
let rem = if actual < predicted { actual } else { actual - 1 };
w.write_bits(rem as u32, 3);
}
}
w.write_ue(plan.chroma_mode as u32); write_cbp_intra(w, cbp);
if cbp != 0 {
w.write_se(fe.qp_delta()); }
fe.nnz_cache_load(mb_x, mb_y);
for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if i4.cbp_luma & (1 << (blk / 4)) != 0 {
let nc = fe.nc_pred(lbx, lby);
let scan16 = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
encode_residual_block(w, &scan16, 16, nc) as u8
} else {
0
};
fe.nnz_cache_set(lbx, lby, total);
fe.nnz_y[by * w4 + bx] = total;
}
} else {
let mb_type = 1 + plan.i16_mode as u32 + 4 * cbp_chroma + if plan.i16_cbp15 { 12 } else { 0 };
w.write_ue(mb_type + mb_type_offset);
w.write_ue(plan.chroma_mode as u32); w.write_se(fe.qp_delta()); fe.nnz_cache_load(mb_x, mb_y);
let nc_dc = fe.nc_pred(0, 0);
let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
encode_residual_block(w, &dc_scan, 16, nc_dc);
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.nnz_cache_set(lbx, lby, 0);
fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
}
if plan.i16_cbp15 {
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let nc = fe.nc_pred(lbx, lby);
let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
let total = encode_residual_block(w, &ac, 15, nc) as u8;
fe.nnz_cache_set(lbx, lby, total);
fe.nnz_y[by * w4 + bx] = total;
}
}
}
if cbp_chroma != 0 {
for c in 0..2 {
encode_residual_block(w, &plan.c_dc_levels[c], 4, -1);
}
}
if cbp_chroma == 2 {
fe.chroma_cache_load(mb_x, mb_y);
let w2 = fe.mb_w * 2;
for c in 0..2 {
for &(bx, by) in &CHROMA_4X4_SCAN_XY {
let nc = fe.chroma_nc_pred(c, bx, by);
let ac = scan_4x4_ac(&plan.c_q_blocks[c][by * 2 + bx]);
let total = encode_residual_block(w, &ac, 15, nc) as u8;
fe.chroma_nnz_cache_set(c, bx, by, total);
fe.nnz_c[c][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total;
}
}
}
}
const CB_NZC_CACHE: [usize; 24] = [
9, 10, 17, 18, 11, 12, 19, 20, 25, 26, 33, 34, 27, 28, 35, 36, 14, 15, 22, 23, 38, 39, 46, 47, ];
const CB_RES_MAXPOS: [i32; 11] = [0, 15, 14, 15, 3, 14, 63, 3, 3, 14, 14];
const CB_RES_MAXC2: [i32; 11] = [0, 4, 4, 4, 3, 4, 4, 3, 3, 4, 4];
const CB_RES_CBF: [usize; 11] = [0, 0, 4, 8, 12, 16, 0, 12, 12, 16, 16];
const CB_RES_MAP: [usize; 11] = [0, 0, 15, 29, 44, 47, 0, 44, 44, 47, 47];
const CB_RES_ONE: [usize; 11] = [0, 0, 10, 20, 30, 39, 0, 30, 30, 39, 39];
const CB_RP_I16_DC: usize = 1;
const CB_RP_I16_AC: usize = 2;
const CB_RP_LUMA_4X4: usize = 3;
const CB_RP_CHROMA_DC: usize = 7;
const CB_RP_CHROMA_AC: usize = 9;
fn cb_unary(cab: &mut CabacEncoder, ctx: usize, off: usize, value: u32) {
if value == 0 {
cab.encode_decision(ctx, 0);
return;
}
cab.encode_decision(ctx, 1);
for _ in 0..value - 1 {
cab.encode_decision(ctx + off, 1);
}
cab.encode_decision(ctx + off, 0);
}
fn cb_exp_bypass(cab: &mut CabacEncoder, mut k: i32, mut n: u32) {
while n >= (1 << k) {
cab.encode_bypass(1);
n -= 1 << k;
k += 1;
}
cab.encode_bypass(0);
while k > 0 {
k -= 1;
cab.encode_bypass((n >> k) & 1);
}
}
fn cb_ueg_level(cab: &mut CabacEncoder, ctx: usize, value: u32) {
if value == 0 {
cab.encode_decision(ctx, 0);
return;
}
let ones = value.min(13);
for _ in 0..ones {
cab.encode_decision(ctx, 1);
}
if value < 13 {
cab.encode_decision(ctx, 0);
} else {
cb_exp_bypass(cab, 0, value - 13);
}
}
fn cb_mb_qp_delta(cab: &mut CabacEncoder, last_delta_qp: &mut i32, delta: i32) {
const O: usize = 60;
let ctx_inc = (*last_delta_qp != 0) as usize;
if delta == 0 {
cab.encode_decision(O + ctx_inc, 0);
} else {
cab.encode_decision(O + ctx_inc, 1);
let code = 2 * delta.unsigned_abs() - (delta > 0) as u32;
cb_unary(cab, O + 2, 1, code - 1);
}
*last_delta_qp = delta;
}
fn cb_chroma_pred_mode(cab: &mut CabacEncoder, ctx_inc: usize, mode: u8) {
const C: usize = 64;
if mode == 0 {
cab.encode_decision(C + ctx_inc, 0);
return;
}
cab.encode_decision(C + ctx_inc, 1);
if mode == 1 {
cab.encode_decision(C + 3, 0);
} else if mode == 2 {
cab.encode_decision(C + 3, 1);
cab.encode_decision(C + 3, 0);
} else {
cab.encode_decision(C + 3, 1);
cab.encode_decision(C + 3, 1);
}
}
fn cb_mb_type_i(
cab: &mut CabacEncoder,
ctx_inc: usize,
use_i4: bool,
i16_mode: u32,
cbp_chroma: u32,
cbp_luma15: bool,
) {
const O: usize = 3;
if use_i4 {
cab.encode_decision(O + ctx_inc, 0); return;
}
cab.encode_decision(O + ctx_inc, 1);
cab.encode_terminate(false); cab.encode_decision(O + 3, cbp_luma15 as u32);
if cbp_chroma != 0 {
cab.encode_decision(O + 4, 1);
cab.encode_decision(O + 5, (cbp_chroma == 2) as u32);
} else {
cab.encode_decision(O + 4, 0);
}
cab.encode_decision(O + 6, (i16_mode >> 1) & 1);
cab.encode_decision(O + 7, i16_mode & 1);
}
fn cb_intra4x4_pred_mode(cab: &mut CabacEncoder, predicted: u8, actual: u8) {
const IPR: usize = 68;
if actual == predicted {
cab.encode_decision(IPR, 1);
} else {
cab.encode_decision(IPR, 0);
let rem = if actual < predicted { actual } else { actual - 1 } as u32;
cab.encode_decision(IPR + 1, rem & 1);
cab.encode_decision(IPR + 1, (rem >> 1) & 1);
cab.encode_decision(IPR + 1, (rem >> 2) & 1);
}
}
fn cb_cbp(cab: &mut CabacEncoder, top: Option<u8>, left: Option<u8>, cbp: u32) {
const CBP: usize = 73;
let t = |m: u32| top.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
let l = |m: u32| left.map_or(0u32, |c| ((c as u32 & m) == 0) as u32);
let nb = |x: u32| (x == 0) as u32;
let b0 = cbp & 1;
let b1 = (cbp >> 1) & 1;
let b2 = (cbp >> 2) & 1;
let b3 = (cbp >> 3) & 1;
cab.encode_decision(CBP + (l(1 << 1) + (t(1 << 2) << 1)) as usize, b0);
cab.encode_decision(CBP + (nb(b0) + (t(1 << 3) << 1)) as usize, b1);
cab.encode_decision(CBP + (l(1 << 3) + (nb(b0) << 1)) as usize, b2);
cab.encode_decision(CBP + (nb(b2) + (nb(b1) << 1)) as usize, b3);
let cbp_chroma = cbp >> 4;
let ct = top.map_or(0u32, |c| ((c >> 4) != 0) as u32);
let cl = left.map_or(0u32, |c| ((c >> 4) != 0) as u32);
cab.encode_decision(CBP + 4 + (cl + (ct << 1)) as usize, (cbp_chroma != 0) as u32);
if cbp_chroma != 0 {
let ct2 = top.map_or(0u32, |c| ((c >> 4) == 2) as u32);
let cl2 = left.map_or(0u32, |c| ((c >> 4) == 2) as u32);
cab.encode_decision(CBP + 8 + (cl2 + (ct2 << 1)) as usize, (cbp_chroma == 2) as u32);
}
}
#[allow(clippy::too_many_arguments)]
fn cb_residual(
cab: &mut CabacEncoder,
nzc: &mut [u8; 48],
cbf_dc: &mut u16,
iz: usize,
rp: usize,
is_intra: bool,
ndc: (Option<u16>, Option<u16>),
coeffs: &[i32],
) -> u32 {
let is_dc = rp == CB_RP_I16_DC || rp == CB_RP_CHROMA_DC || rp == CB_RP_CHROMA_DC + 1;
let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
let scan = CB_NZC_CACHE[iz.min(23)];
if is_dc {
if let Some(t) = ndc.0 {
nb = ((t >> rp) & 1) as u8;
}
if let Some(l) = ndc.1 {
na = ((l >> rp) & 1) as u8;
}
} else {
if nzc[scan - 8] != 0xff {
nb = (nzc[scan - 8] != 0) as u8;
}
if nzc[scan - 1] != 0xff {
na = (nzc[scan - 1] != 0) as u8;
}
}
let maxpos = CB_RES_MAXPOS[rp] as usize;
let coeff_num = coeffs[..=maxpos].iter().filter(|&&c| c != 0).count() as u32;
let cbf = coeff_num != 0;
cab.encode_decision(85 + CB_RES_CBF[rp] + (na + (nb << 1)) as usize, cbf as u32);
if !cbf {
if !is_dc {
nzc[scan] = 0;
}
return 0;
}
if is_dc {
*cbf_dc |= 1 << rp;
}
let map = 105 + CB_RES_MAP[rp];
let last = 166 + CB_RES_MAP[rp];
let lastnz = (0..=maxpos).rev().find(|&i| coeffs[i] != 0).unwrap();
for i in 0..maxpos {
let s = coeffs[i] != 0;
cab.encode_decision(map + i, s as u32);
if s {
let is_last = i == lastnz;
cab.encode_decision(last + i, is_last as u32);
if is_last {
break;
}
}
}
let one = 227 + CB_RES_ONE[rp];
let abs = 232 + CB_RES_ONE[rp];
let maxc2 = CB_RES_MAXC2[rp];
let (mut c1, mut c2) = (1i32, 0i32);
for i in (0..=maxpos).rev() {
if coeffs[i] != 0 {
let av = coeffs[i].unsigned_abs();
let gt1 = av > 1;
cab.encode_decision(one + c1 as usize, gt1 as u32);
if gt1 {
cb_ueg_level(cab, abs + c2 as usize, av - 2);
c2 = (c2 + 1).min(maxc2);
c1 = 0;
} else if c1 != 0 {
c1 = (c1 + 1).min(4);
}
cab.encode_bypass((coeffs[i] < 0) as u32);
}
}
if !is_dc {
nzc[scan] = coeff_num as u8;
}
coeff_num
}
fn cb_build_nzc(mb_nzc: &[[u8; 24]], top: Option<usize>, left: Option<usize>) -> [u8; 48] {
let mut nzc = [0xffu8; 48];
if let Some(t) = top {
let tn = mb_nzc[t];
nzc[1..5].copy_from_slice(&tn[12..16]);
(nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
(nzc[6], nzc[7]) = (tn[20], tn[21]);
(nzc[30], nzc[31]) = (tn[22], tn[23]);
}
if let Some(l) = left {
let ln = mb_nzc[l];
(nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
(nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
}
nzc
}
fn cb_export_nzc(nzc: &[u8; 48]) -> [u8; 24] {
let mut mn = [0u8; 24];
for k in 0..4 {
mn[k] = nzc[9 + k];
mn[4 + k] = nzc[17 + k];
mn[8 + k] = nzc[25 + k];
mn[12 + k] = nzc[33 + k];
}
(mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
(mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
for v in mn.iter_mut() {
if *v == 0xff {
*v = 0;
}
}
mn
}
struct CabacState {
cat: Vec<u8>, cmode: Vec<i32>, mb_cbp: Vec<u8>, cbf_dc: Vec<u16>, mb_nzc: Vec<[u8; 24]>, mb_mvd: Vec<[[i16; 2]; 16]>, mb_ref: Vec<[i8; 16]>, mb_mvd1: Vec<[[i16; 2]; 16]>, mb_ref1: Vec<[i8; 16]>, mb_skip: Vec<bool>, mb_direct: Vec<bool>, last_delta_qp: i32,
}
impl CabacState {
fn new(n: usize) -> Self {
CabacState {
cat: vec![0; n],
cmode: vec![0; n],
mb_cbp: vec![0; n],
cbf_dc: vec![0; n],
mb_nzc: vec![[0u8; 24]; n],
mb_mvd: vec![[[0i16; 2]; 16]; n],
mb_ref: vec![[-1i8; 16]; n],
mb_mvd1: vec![[[0i16; 2]; 16]; n],
mb_ref1: vec![[-1i8; 16]; n],
mb_skip: vec![false; n],
mb_direct: vec![false; n],
last_delta_qp: 0,
}
}
}
fn emit_mb_cabac_i(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
plan: &MbPlan,
mb_x: usize,
mb_y: usize,
) {
let mb_w = fe.mb_w;
let addr = mb_y * mb_w + mb_x;
let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
let li = left.map_or(0, |a| (cs.cat[a] >= 2) as usize);
let ti = top.map_or(0, |a| (cs.cat[a] >= 2) as usize);
let acct = crate::bitacct::enabled();
let t0 = if acct { cab.pos() } else { 0 };
if plan.use_i4 {
cb_mb_type_i(cab, li + ti, true, 0, 0, false);
} else {
cb_mb_type_i(cab, li + ti, false, plan.i16_mode as u32, plan.cbp_chroma, plan.i16_cbp15);
}
if acct {
crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
}
let t1 = if acct { cab.pos() } else { 0 };
emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
if acct {
crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t1);
}
}
#[allow(clippy::too_many_arguments)]
fn emit_intra_body_cabac(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
plan: &MbPlan,
mb_x: usize,
mb_y: usize,
addr: usize,
top: Option<usize>,
left: Option<usize>,
) {
let w4 = fe.mb_w * 4;
let cbp_chroma = plan.cbp_chroma;
let cci = left.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize)
+ top.map_or(0, |a| (1..=3).contains(&cs.cmode[a]) as usize);
let mut nzc;
let mut cbfdc = 0u16;
let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
if !plan.use_i4 {
cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
cs.cmode[addr] = plan.chroma_mode as i32;
cs.cat[addr] = 2;
cs.mb_cbp[addr] = ((cbp_chroma as u8) << 4) | if plan.i16_cbp15 { 15 } else { 0 };
nzc = cb_build_nzc(&cs.mb_nzc, top, left);
let delta = fe.qp_delta();
cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
let dc_scan = scan_4x4_dcac(&plan.i16_dc_levels);
cb_residual(cab, &mut nzc, &mut cbfdc, 0, CB_RP_I16_DC, true, ndc, &dc_scan);
for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if plan.i16_cbp15 {
let ac = scan_4x4_ac(&plan.i16_q[lby * 4 + lbx]);
cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_I16_AC, true, ndc, &ac)
} else {
nzc[CB_NZC_CACHE[iz]] = 0;
0
};
fe.nnz_y[by * w4 + bx] = total as u8;
}
cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
} else {
let i4 = plan.i4.as_ref().unwrap();
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let predicted = predict_i4_mode(fe, bx, by);
cb_intra4x4_pred_mode(cab, predicted, i4.modes[lby * 4 + lbx]);
}
cb_chroma_pred_mode(cab, cci, plan.chroma_mode);
cs.cmode[addr] = plan.chroma_mode as i32;
cs.cat[addr] = 0;
let cbp = i4.cbp_luma | (cbp_chroma << 4);
cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
cs.mb_cbp[addr] = cbp as u8;
nzc = cb_build_nzc(&cs.mb_nzc, top, left);
if cbp == 0 {
cs.last_delta_qp = 0;
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
}
} else {
let delta = fe.qp_delta();
cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
for id8 in 0..4usize {
for id4 in 0..4usize {
let iz = id8 * 4 + id4;
let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if i4.cbp_luma & (1 << id8) != 0 {
let sc = scan_4x4_dcac(&i4.q[lby * 4 + lbx]);
cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, true, ndc, &sc)
} else {
nzc[CB_NZC_CACHE[iz]] = 0;
0
};
fe.nnz_y[by * w4 + bx] = total as u8;
}
}
cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, true, plan.cbp_chroma, &plan.c_dc_levels, &plan.c_q_blocks, mb_x, mb_y);
}
}
cs.cbf_dc[addr] = cbfdc;
cs.mb_nzc[addr] = cb_export_nzc(&nzc);
}
#[allow(clippy::too_many_arguments)]
fn cb_emit_chroma_residual(
cab: &mut CabacEncoder,
fe: &mut FrameEncoder,
nzc: &mut [u8; 48],
cbfdc: &mut u16,
ndc: (Option<u16>, Option<u16>),
is_intra: bool,
cbp_chroma: u32,
c_dc_levels: &[[i32; 4]; 2],
c_q: &[[[i32; 16]; 4]; 2],
mb_x: usize,
mb_y: usize,
) {
let w2 = fe.mb_w * 2;
if cbp_chroma >= 1 {
for i in 0..2usize {
cb_residual(cab, nzc, cbfdc, 16 + i * 4, CB_RP_CHROMA_DC + i, is_intra, ndc, &c_dc_levels[i]);
}
}
if cbp_chroma == 2 {
for i in 0..2usize {
for (id4, &(bx, by)) in CHROMA_4X4_SCAN_XY.iter().enumerate() {
let ac = scan_4x4_ac(&c_q[i][by * 2 + bx]);
let total = cb_residual(
cab, nzc, cbfdc, 16 + i * 4 + id4, CB_RP_CHROMA_AC + i, is_intra, ndc, &ac,
);
fe.nnz_c[i][(mb_y * 2 + by) * w2 + (mb_x * 2 + bx)] = total as u8;
}
}
}
}
pub fn encode_slice_data_cabac_intra(
w: &mut BitWriter,
cfg: &EncoderConfig,
frame: &YuvFrame,
qp: u8,
qpo: &[i32],
) -> crate::RefFrame {
let mut fe = FrameEncoder::new(cfg);
fe.qp = qp;
fe.qpc = chroma_qp(qp);
fe.cur_qp = qp;
if cfg.cabac_dz_div > 0 {
fe.idz = cfg.cabac_dz_div; }
let (sy, su, sv) = coded_source(cfg, frame);
let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
apply_mbtree_qpo(&mut aq_qp, qpo); fe.cur_qp = qp;
let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
fe.rdoq_strength = if cfg.gop_size <= 1 { cfg.cabac_rdoq } else { 0.0 };
let mut cab = CabacEncoder::new(qp as i32, 0, true);
let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
let total = fe.mb_w * fe.mb_h;
for mb_y in 0..fe.mb_h {
for mb_x in 0..fe.mb_w {
let mb_idx = mb_y * fe.mb_w + mb_x;
fe.qp = aq_qp[mb_idx];
fe.qpc = chroma_qp(aq_qp[mb_idx]);
let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
emit_mb_cabac_i(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
mb_qpy[mb_idx] = fe.cur_qp;
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_terminate(mb_idx + 1 == total);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
}
}
while !w.is_byte_aligned() {
w.write_bit(true);
}
for b in cab.into_bytes() {
w.write_bits(b as u32, 8);
}
let ref_id: Vec<i32> = fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }).collect();
let info = rusty_h264_common::deblock::BlockInfo {
inter: &fe.inter_y,
nnz: &fe.nnz_y,
mv: &fe.mv_y,
ref_id: &ref_id,
mv1: &[],
ref_id1: &[],
w4: fe.mb_w * 4,
t8x8: &[],
bs: &[],
};
rusty_h264_common::deblock::filter_frame(
&mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
);
let w4 = fe.mb_w * 4;
crate::RefFrame {
y: fe.rec_y,
u: fe.rec_u,
v: fe.rec_v,
poc: 0,
frame_num: 0,
mv: fe.mv_y,
ref_idx: fe.ref_idx_y,
w4,
hpel: std::sync::OnceLock::new(),
}
}
const CB_CACHE30: [usize; 16] = [7, 8, 13, 14, 9, 10, 15, 16, 19, 20, 25, 26, 21, 22, 27, 28];
const CB_G_SCAN4: [usize; 16] = [0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15];
fn cb_ueg_mv(cab: &mut CabacEncoder, base: usize, v: u32) {
const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
if v == 0 {
cab.encode_decision(base, 0);
return;
}
cab.encode_decision(base, 1);
if v <= 7 {
let mut count = 1;
for _ in 0..v - 1 {
cab.encode_decision(base + P2C[count], 1);
count += 1;
}
cab.encode_decision(base + P2C[count], 0);
} else {
let mut count = 1;
for _ in 0..7 {
cab.encode_decision(base + P2C[count], 1);
count += 1;
}
let tb = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cb_exp_bypass(cab, 3, v - 8);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::MvdBypass, cab.pos() - tb);
}
}
}
fn cb_mvd(cab: &mut CabacEncoder, comp: usize, ctx_inc: usize, d: i32) {
let th = if crate::bitacct::enabled() { cab.pos() } else { u64::MAX };
let base = 40 + comp * 7;
if d == 0 {
cab.encode_decision(base + ctx_inc, 0);
if th != u64::MAX {
crate::bitacct::add_mvd_sample(0, cab.pos() - th);
}
return;
}
cab.encode_decision(base + ctx_inc, 1);
cb_ueg_mv(cab, base + 3, d.unsigned_abs() - 1); let ts = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_bypass((d < 0) as u32);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::MvdSign, cab.pos() - ts);
}
if th != u64::MAX {
crate::bitacct::add_mvd_sample(d.unsigned_abs(), cab.pos() - th);
}
}
fn cb_mb_skip(cab: &mut CabacEncoder, ctx_inc: usize, skip: bool) {
cab.encode_decision(ctx_inc, skip as u32);
}
fn cb_ref_idx(cab: &mut CabacEncoder, ctx0: usize, r: u32) {
const B: usize = 54;
let mut v = r;
let mut bin_idx = 0u32;
loop {
let bin = (v > 0) as u32;
let ctx = match bin_idx {
0 => ctx0,
1 => 4,
_ => 5,
};
cab.encode_decision(B + ctx, bin);
if bin == 0 {
break;
}
v -= 1;
bin_idx += 1;
}
}
fn cb_mb_type_p_inter(cab: &mut CabacEncoder, mode: u8) {
const S: usize = 11;
cab.encode_decision(S + 3, 0); match mode {
0 => {
cab.encode_decision(S + 4, 0);
cab.encode_decision(S + 5, 0);
}
3 => {
cab.encode_decision(S + 4, 0);
cab.encode_decision(S + 5, 1);
}
1 => {
cab.encode_decision(S + 4, 1);
cab.encode_decision(S + 6, 1);
}
_ => {
cab.encode_decision(S + 4, 1);
cab.encode_decision(S + 6, 0);
}
}
}
fn cb_sub_mb_type_p(cab: &mut CabacEncoder, sub_type: u8) {
const S: usize = 21;
match sub_type {
0 => cab.encode_decision(S, 1),
_ => unreachable!("only 8x8 sub_mb_type (0) emitted"),
}
}
fn cb_mb_type_p_intra(cab: &mut CabacEncoder, plan: &MbPlan) {
const S: usize = 11;
cab.encode_decision(S + 3, 1); if plan.use_i4 {
cab.encode_decision(S + 6, 0); return;
}
cab.encode_decision(S + 6, 1); cab.encode_terminate(false); cab.encode_decision(S + 7, plan.i16_cbp15 as u32);
if plan.cbp_chroma != 0 {
cab.encode_decision(S + 8, 1);
cab.encode_decision(S + 8, (plan.cbp_chroma == 2) as u32);
} else {
cab.encode_decision(S + 8, 0);
}
cab.encode_decision(S + 9, (plan.i16_mode as u32 >> 1) & 1);
cab.encode_decision(S + 9, plan.i16_mode as u32 & 1);
}
fn p_partition_layout(mode: u8) -> &'static [(usize, &'static [usize])] {
match mode {
1 => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7]), (8, &[8, 9, 10, 11, 12, 13, 14, 15])],
2 => &[(0, &[0, 1, 2, 3, 8, 9, 10, 11]), (4, &[4, 5, 6, 7, 12, 13, 14, 15])],
3 => &[(0, &[0, 1, 2, 3]), (4, &[4, 5, 6, 7]), (8, &[8, 9, 10, 11]), (12, &[12, 13, 14, 15])],
_ => &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])],
}
}
#[allow(clippy::too_many_arguments)]
fn cb_emit_mvd_partition(
cab: &mut CabacEncoder,
part_idx: usize,
zblocks: &[usize],
mvdc: &mut [[i16; 2]; 30],
refc: &mut [i8; 30],
mmvd: &mut [[i16; 2]; 16],
mref: &mut [i8; 16],
mvd: (i32, i32),
ref_idx: i8, ) {
let s = CB_CACHE30[part_idx];
let ctx = |comp: usize| -> usize {
let mut a = 0i32;
if refc[s - 6] >= 0 {
a += mvdc[s - 6][comp].unsigned_abs() as i32;
}
if refc[s - 1] >= 0 {
a += mvdc[s - 1][comp].unsigned_abs() as i32;
}
if a >= 3 {
1 + (a > 32) as usize
} else {
0
}
};
cb_mvd(cab, 0, ctx(0), mvd.0);
cb_mvd(cab, 1, ctx(1), mvd.1);
let (mx, my) = (mvd.0 as i16, mvd.1 as i16);
for &zb in zblocks {
mvdc[CB_CACHE30[zb]] = [mx, my];
refc[CB_CACHE30[zb]] = ref_idx;
mmvd[CB_G_SCAN4[zb]] = [mx, my];
mref[CB_G_SCAN4[zb]] = ref_idx;
}
}
fn emit_mb_cabac_p_inter(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
mode: u8,
plan: &InterPlan,
mb_x: usize,
mb_y: usize,
num_refs: usize,
) {
let mb_w = fe.mb_w;
let addr = mb_y * mb_w + mb_x;
let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
let acct = crate::bitacct::enabled();
let mut t0 = if acct { cab.pos() } else { 0 };
cb_mb_type_p_inter(cab, mode);
if mode == 3 {
for _ in 0..4 {
cb_sub_mb_type_p(cab, 0);
}
}
if acct {
crate::bitacct::add(crate::bitacct::B::MbType, cab.pos() - t0);
t0 = cab.pos();
}
let mut mvdc = [[0i16; 2]; 30];
let mut refc = [-1i8; 30];
cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc, &mut mvdc, top, left, addr, mb_w);
let mut mmvd = [[0i16; 2]; 16];
let mut mref = [0i8; 16];
let layout = p_partition_layout(mode);
if num_refs > 1 {
for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
let r = plan.plan_refs[part];
let s = CB_CACHE30[part_idx];
let ctx0 = (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
cb_ref_idx(cab, ctx0, r as u32);
for &zb in zblocks {
refc[CB_CACHE30[zb]] = r as i8;
}
}
}
if acct {
crate::bitacct::add(crate::bitacct::B::RefIdx, cab.pos() - t0);
t0 = cab.pos();
}
for (part, &(part_idx, zblocks)) in layout.iter().enumerate() {
cb_emit_mvd_partition(
cab, part_idx, zblocks, &mut mvdc, &mut refc, &mut mmvd, &mut mref, plan.mvds[part],
plan.plan_refs[part] as i8,
);
}
if acct {
crate::bitacct::add(crate::bitacct::B::Mvd, cab.pos() - t0);
}
cs.mb_mvd[addr] = mmvd;
cs.mb_ref[addr] = mref;
cs.cat[addr] = 100;
cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
}
#[allow(clippy::too_many_arguments)]
fn cb_emit_inter_residual(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
plan: &InterPlan,
mb_x: usize,
mb_y: usize,
addr: usize,
top: Option<usize>,
left: Option<usize>,
) {
let w4 = fe.mb_w * 4;
let cbp = plan.cbp;
let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
let acct = crate::bitacct::enabled();
let mut t0 = if acct { cab.pos() } else { 0 };
cb_cbp(cab, top.map(|a| cs.mb_cbp[a]), left.map(|a| cs.mb_cbp[a]), cbp);
if acct {
crate::bitacct::add(crate::bitacct::B::Cbp, cab.pos() - t0);
}
cs.mb_cbp[addr] = cbp as u8;
let mut nzc = cb_build_nzc(&cs.mb_nzc, top, left);
let mut cbfdc = 0u16;
let ndc = (top.map(|a| cs.cbf_dc[a]), left.map(|a| cs.cbf_dc[a]));
if cbp == 0 {
cs.last_delta_qp = 0;
for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
fe.nnz_y[(mb_y * 4 + lby) * w4 + (mb_x * 4 + lbx)] = 0;
}
} else {
let delta = fe.qp_delta();
if acct { t0 = cab.pos(); }
cb_mb_qp_delta(cab, &mut cs.last_delta_qp, delta);
if acct {
crate::bitacct::add(crate::bitacct::B::QpDelta, cab.pos() - t0);
t0 = cab.pos();
}
for id8 in 0..4usize {
for id4 in 0..4usize {
let iz = id8 * 4 + id4;
let (lbx, lby) = LUMA_4X4_SCAN_XY[iz];
let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
let total = if cbp_luma & (1 << id8) != 0 {
let sc = scan_4x4_dcac(&plan.q_blocks[lby * 4 + lbx]);
cb_residual(cab, &mut nzc, &mut cbfdc, iz, CB_RP_LUMA_4X4, false, ndc, &sc)
} else {
nzc[CB_NZC_CACHE[iz]] = 0;
0
};
fe.nnz_y[by * w4 + bx] = total as u8;
}
}
if acct {
crate::bitacct::add(crate::bitacct::B::ResidLuma, cab.pos() - t0);
t0 = cab.pos();
}
cb_emit_chroma_residual(cab, fe, &mut nzc, &mut cbfdc, ndc, false, cbp_chroma, &plan.c_dc_levels, &plan.c_q, mb_x, mb_y);
if acct {
crate::bitacct::add(crate::bitacct::B::ResidChroma, cab.pos() - t0);
}
}
cs.cbf_dc[addr] = cbfdc;
cs.mb_nzc[addr] = cb_export_nzc(&nzc);
}
fn emit_mb_cabac_p_intra(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
plan: &MbPlan,
mb_x: usize,
mb_y: usize,
) {
let mb_w = fe.mb_w;
let addr = mb_y * mb_w + mb_x;
let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
let acct = crate::bitacct::enabled();
let t0 = if acct { cab.pos() } else { 0 };
cb_mb_type_p_intra(cab, plan);
emit_intra_body_cabac(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
if acct {
crate::bitacct::add(crate::bitacct::B::IntraBody, cab.pos() - t0);
}
}
fn emit_p_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
let sctx = 11
+ left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
+ top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
let t0 = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cb_mb_skip(cab, sctx, true);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
}
cs.mb_skip[addr] = true;
cs.cat[addr] = 100;
cs.last_delta_qp = 0;
}
pub fn encode_slice_data_cabac_p(
w: &mut BitWriter,
cfg: &EncoderConfig,
frame: &YuvFrame,
qp: u8,
refs: &[crate::RefFrame],
qpo: &[i32],
) -> crate::RefFrame {
let mut fe = FrameEncoder::new(cfg);
fe.qp = qp;
fe.qpc = chroma_qp(qp);
fe.cur_qp = qp;
if cfg.cabac_dz_div > 0 {
fe.idz = cfg.cabac_dz_div; }
let (sy, su, sv) = coded_source(cfg, frame);
let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let num_refs = refs.len();
if fe.me_wide && !refs.is_empty() {
let coh = global_mc_residual(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
if std::env::var("RFF_ME_COH_DBG").is_ok() {
eprintln!("ME_COH qp{qp} residual={coh:.2}");
}
if coh < fe.me_wide_coh {
fe.me_wide = false;
}
}
if fe.me_wide && !refs.is_empty() && (me_wide_hr_thresh() > 0.0 || me_wide_hr_dbg()) {
let hr = me_wide_headroom(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
if me_wide_hr_dbg() {
eprintln!("ME_HR qp{qp} headroom={hr:.2}");
}
if me_wide_hr_thresh() > 0.0 && hr < me_wide_hr_thresh() {
fe.me_wide = false;
}
}
if me_sadfp_mode() == 1 && !fe.fast && !refs.is_empty() {
let (mg, dc) = b2_mgain(&sy, fe.cw, fe.mb_h * 16, &refs[0].y);
if me_sadt_dbg() {
eprintln!("B2_MG qp{qp} mgain={mg:.3} dcfrac={dc:.3}");
}
fe.sadfp = mg >= me_sadt() && dc <= me_sad_dcmax();
if mv_smooth_mode() == 1 {
fe.mv_smooth = mg >= mv_smooth_t() && dc <= me_sad_dcmax();
}
let smg = split_mg();
if smg > 0.0 {
fe.do_splits = mg >= smg;
}
}
if fe.satd_q > 0.0 {
let mut vars: Vec<i64> = (0..fe.mb_h)
.flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
.map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
.collect();
vars.sort_unstable();
let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
fe.satd_var_thresh = vars[idx];
}
let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
apply_mbtree_qpo(&mut aq_qp, qpo); fe.cur_qp = qp;
let mut mb_qpy = vec![qp; fe.mb_w * fe.mb_h];
let mut greedy_free = 0usize;
let mut greedy_seen = 0usize;
let mut greedy_on = fe.greedy_min_free == 0;
let greedy_learn = (fe.mb_w * fe.mb_h / 8).max(64);
let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false); let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
let total = fe.mb_w * fe.mb_h;
let _g_loop = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMbLoop);
for mb_y in 0..fe.mb_h {
for mb_x in 0..fe.mb_w {
let mb_idx = mb_y * fe.mb_w + mb_x;
let addr = mb_idx;
let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
fe.qp = aq_qp[mb_idx];
fe.qpc = chroma_qp(aq_qp[mb_idx]);
let mut inter: Option<InterChoice> = None;
let mut did_skip = false;
if num_refs > 0 {
let mv_skip = fe.skip_mv(mb_x, mb_y);
let skip_y = fe.skip_predict_luma(refs, mb_x, mb_y, mv_skip);
let luma_free = fe.skip_luma_is_free(&sy, mb_x, mb_y, &skip_y);
let skip_c = if luma_free || !fe.fast {
fe.skip_predict_chroma(refs, mb_x, mb_y, mv_skip)
} else {
[[0u8; 64]; 2]
};
let is_free = luma_free && fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &skip_c);
let skip_sad = if fe.fast {
0
} else {
let (lx, ly) = (mb_x * 16, mb_y * 16);
let mut s = 0u32;
for dy in 0..16 {
let src = &sy[(ly + dy) * fe.cw + lx..][..16];
let p = &skip_y[dy * 16..][..16];
s += src.iter().zip(p).map(|(&a, &b)| a.abs_diff(b) as u32).sum::<u32>();
}
s
};
greedy_seen += 1;
if greedy_seen >= greedy_learn {
greedy_on = fe.greedy_min_free == 0
|| greedy_free * 100 >= greedy_seen * fe.greedy_min_free as usize;
}
if is_free {
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
if !fe.fast {
fe.mb_was_skip[mb_idx] = true;
fe.mb_skip_sad[mb_idx] = skip_sad;
}
greedy_free += 1;
did_skip = true;
} else {
let (lx, ly) = (mb_x * 16, mb_y * 16);
let nb = {
let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncMvPred);
fe.mv_neighbors_block(mb_x as isize * 4, mb_y as isize * 4, 4)
};
let lme = lambda.sqrt() * cfg.cabac_lambda_scale;
if fe.fast {
fe.mb_use_satd = fe.satd_q > 0.0
&& mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
let (r16, mv16, cost_inter) =
fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
let cost_intra = if fe.mb_use_satd {
fe.best_i16_satd(&sy, mb_x, mb_y)
} else {
fe.best_i16_sad(&sy, mb_x, mb_y)
} + (lme * fe.tune_intra_penalty) as i64;
inter = if cost_intra < cost_inter {
None
} else {
Some((0, vec![(r16, mv16)]))
};
} else {
if fe.greedy_skip && greedy_on && skip_sad < fe.pred_skip_sad(mb_x, mb_y) {
fe.commit_skip(mb_x, mb_y, mv_skip, &skip_y, &skip_c);
fe.mb_was_skip[mb_idx] = true;
fe.mb_skip_sad[mb_idx] = skip_sad;
did_skip = true;
} else {
let (r16, mv16, c16) =
fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 16, &[], lme);
let mut best_c = c16;
let mut pick: Option<InterChoice> = Some((0, vec![(r16, mv16)]));
const QSTEP16: [i64; 6] = [10, 11, 13, 14, 16, 18];
let qstep16 = QSTEP16[(fe.qp % 6) as usize] << (fe.qp / 6);
let split_gate = ((30 * (qstep16 + 160)) >> 3) * 2;
let split_t = split_t();
if fe.do_splits && c16 > split_gate && (split_t <= 0.0 || (c16 as f64) >= split_t * lme) {
let (rt, mvt, ct) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 16, 8, &[mv16], lme);
let (rb, mvb, cb) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly + 8, 16, 8, &[mv16], lme);
let (rl, mvl, cl) = fe.best_part(refs, &sy, &nb, num_refs, lx, ly, 8, 16, &[mv16], lme);
let (rr, mvr, cr) = fe.best_part(refs, &sy, &nb, num_refs, lx + 8, ly, 8, 16, &[mv16], lme);
if ct + cb < best_c {
best_c = ct + cb;
pick = Some((1u8, vec![(rt, mvt), (rb, mvb)]));
}
if cl + cr < best_c {
best_c = cl + cr;
pick = Some((2u8, vec![(rl, mvl), (rr, mvr)]));
}
if fe.sub8x8 {
let mut c8 = (lme * 4.0) as i64;
let mut p8 = Vec::with_capacity(4);
for &(qx, qy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
let (r, mv, c) = fe.best_part(
refs, &sy, &nb, num_refs, lx + qx, ly + qy, 8, 8, &[mv16], lme,
);
c8 += c;
p8.push((r, mv));
}
if c8 < best_c {
best_c = c8;
pick = Some((3u8, p8));
}
}
}
if fe.sp_defer.get() {
if let Some((mode, parts)) = pick.as_mut() {
let regions: &[(usize, usize, usize, usize)] = match mode {
1 => &[(0, 0, 16, 8), (0, 8, 16, 8)],
2 => &[(0, 0, 8, 16), (8, 0, 8, 16)],
3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
_ => &[(0, 0, 16, 16)],
};
let mut tot = if *mode == 3 { (lme * 4.0) as i64 } else { 0 };
for (i, &(qx, qy, pw, ph)) in regions.iter().enumerate() {
let (r, mv) = parts[i];
let (m2, c2) = fe.refine_part(
refs, &sy, &nb, num_refs, lx + qx, ly + qy, pw, ph, lme, r, mv,
);
parts[i] = (r, m2);
tot += c2;
}
best_c = tot;
}
}
let c_intra = fe.best_i16_satd(&sy, mb_x, mb_y)
+ (lme * fe.tune_intra_penalty) as i64;
inter = if c_intra < best_c { None } else { pick };
fe.mb_was_skip[mb_idx] = false;
fe.mb_skip_sad[mb_idx] = skip_sad;
}
}
}
}
if did_skip {
emit_p_skip_cabac(&mut cab, &mut cs, addr, top, left);
mb_qpy[mb_idx] = fe.cur_qp;
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_terminate(mb_idx + 1 == total);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
continue;
}
let sctx = 11
+ left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
+ top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
let tskip = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cb_mb_skip(&mut cab, sctx, false);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
}
cs.mb_skip[addr] = false;
match inter {
Some((mode, parts)) => {
let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, mode, &parts, None);
let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
emit_mb_cabac_p_inter(&mut fe, &mut cab, &mut cs, mode, &plan, mb_x, mb_y, num_refs);
}
None => {
let plan = plan_mb(&mut fe, mb_x, mb_y, &sy, &su, &sv);
let _ge = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EncEmit);
emit_mb_cabac_p_intra(&mut fe, &mut cab, &mut cs, &plan, mb_x, mb_y);
}
}
mb_qpy[mb_idx] = fe.cur_qp;
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_terminate(mb_idx + 1 == total);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
}
}
while !w.is_byte_aligned() {
w.write_bit(true);
}
for b in cab.into_bytes() {
w.write_bits(b as u32, 8);
}
let ref_id: Vec<i32> = fe.ref_idx_y.iter().map(|&r| if r >= 0 { r } else { i32::MIN }).collect();
let info = rusty_h264_common::deblock::BlockInfo {
inter: &fe.inter_y,
nnz: &fe.nnz_y,
mv: &fe.mv_y,
ref_id: &ref_id,
mv1: &[],
ref_id1: &[],
w4: fe.mb_w * 4,
t8x8: &[],
bs: &[],
};
rusty_h264_common::deblock::filter_frame(
&mut fe.rec_y, &mut fe.rec_u, &mut fe.rec_v, fe.mb_w, fe.mb_h, &mb_qpy, 0, 0, 0, &info,
);
let w4 = fe.mb_w * 4;
crate::RefFrame {
y: fe.rec_y,
u: fe.rec_u,
v: fe.rec_v,
poc: 0,
frame_num: 0,
mv: fe.mv_y,
ref_idx: fe.ref_idx_y,
w4,
hpel: std::sync::OnceLock::new(),
}
}
fn cb_fill_inter_cache(
mb_ref: &[[i8; 16]],
mb_mvd: &[[[i16; 2]; 16]],
refc: &mut [i8; 30],
mvdc: &mut [[i16; 2]; 30],
top: Option<usize>,
left: Option<usize>,
addr: usize,
mb_w: usize,
) {
if let Some(l) = left {
for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
refc[ci] = mb_ref[l][bi];
mvdc[ci] = mb_mvd[l][bi];
}
}
if let Some(t) = top {
for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
refc[ci] = mb_ref[t][bi];
mvdc[ci] = mb_mvd[t][bi];
}
}
let mb_x = addr % mb_w;
let mb_y = addr / mb_w;
if mb_x > 0 && mb_y > 0 {
let a = addr - mb_w - 1;
(refc[0], mvdc[0]) = (mb_ref[a][15], mb_mvd[a][15]);
}
if mb_y > 0 && mb_x + 1 < mb_w {
let a = addr - mb_w + 1;
(refc[5], mvdc[5]) = (mb_ref[a][12], mb_mvd[a][12]);
}
}
fn cb_mb_type_b(cab: &mut CabacEncoder, ctx_inc: usize, dir: u8) {
const B: usize = 27;
match dir {
0 => cab.encode_decision(B + ctx_inc, 0), 1 => {
cab.encode_decision(B + ctx_inc, 1);
cab.encode_decision(B + 3, 0);
cab.encode_decision(B + 5, 0); }
2 => {
cab.encode_decision(B + ctx_inc, 1);
cab.encode_decision(B + 3, 0);
cab.encode_decision(B + 5, 1); }
_ => {
cab.encode_decision(B + ctx_inc, 1);
cab.encode_decision(B + 3, 1);
cab.encode_decision(B + 4, 0);
cab.encode_decision(B + 5, 0);
cab.encode_decision(B + 5, 0);
cab.encode_decision(B + 5, 0);
}
}
}
const CB_ALL16: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
fn emit_mb_cabac_b(
fe: &mut FrameEncoder,
cab: &mut CabacEncoder,
cs: &mut CabacState,
dir: u8,
plan: &InterPlan,
mb_x: usize,
mb_y: usize,
) {
let mb_w = fe.mb_w;
let addr = mb_y * mb_w + mb_x;
let top = if mb_y > 0 { Some(addr - mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
let bci = left.map_or(0, |a| (!cs.mb_direct[a]) as usize)
+ top.map_or(0, |a| (!cs.mb_direct[a]) as usize);
cb_mb_type_b(cab, bci, dir);
let mut mvdc0 = [[0i16; 2]; 30];
let mut refc0 = [-1i8; 30];
let mut mvdc1 = [[0i16; 2]; 30];
let mut refc1 = [-1i8; 30];
cb_fill_inter_cache(&cs.mb_ref, &cs.mb_mvd, &mut refc0, &mut mvdc0, top, left, addr, mb_w);
cb_fill_inter_cache(&cs.mb_ref1, &cs.mb_mvd1, &mut refc1, &mut mvdc1, top, left, addr, mb_w);
let mut mmvd0 = [[0i16; 2]; 16];
let mut mref0 = [-1i8; 16];
let mut mmvd1 = [[0i16; 2]; 16];
let mut mref1 = [-1i8; 16];
let (use0, use1) = (dir == 1 || dir == 3, dir == 2 || dir == 3);
if dir == 0 {
mref0 = [0i8; 16];
mref1 = [0i8; 16];
} else {
let mut k = 0;
if use0 {
cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc0, &mut refc0, &mut mmvd0, &mut mref0, plan.mvds[k], 0);
k += 1;
}
if use1 {
cb_emit_mvd_partition(cab, 0, &CB_ALL16, &mut mvdc1, &mut refc1, &mut mmvd1, &mut mref1, plan.mvds[k], 0);
}
}
cs.mb_mvd[addr] = mmvd0;
cs.mb_ref[addr] = mref0;
cs.mb_mvd1[addr] = mmvd1;
cs.mb_ref1[addr] = mref1;
cs.mb_direct[addr] = dir == 0;
cs.cat[addr] = 100;
cb_emit_inter_residual(fe, cab, cs, plan, mb_x, mb_y, addr, top, left);
}
fn emit_b_skip_cabac(cab: &mut CabacEncoder, cs: &mut CabacState, addr: usize, top: Option<usize>, left: Option<usize>) {
let sctx = 24
+ left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
+ top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
let t0 = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cb_mb_skip(cab, sctx, true);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - t0);
}
cs.mb_skip[addr] = true;
cs.cat[addr] = 100;
cs.mb_direct[addr] = true;
cs.mb_ref[addr] = [0i8; 16];
cs.mb_ref1[addr] = [0i8; 16];
cs.last_delta_qp = 0;
}
#[allow(clippy::too_many_arguments)]
pub fn encode_slice_data_cabac_b(
w: &mut BitWriter,
cfg: &EncoderConfig,
frame: &YuvFrame,
qp: u8,
poc: i32,
l0: &crate::RefFrame,
l1: &crate::RefFrame,
qpo: &[i32],
) {
let mut fe = FrameEncoder::new(cfg);
fe.qp = qp;
fe.qpc = chroma_qp(qp);
fe.cur_qp = qp;
if cfg.cabac_dz_div > 0 {
fe.idz = cfg.cabac_dz_div; }
fe.bi_w = implicit_bi_weights(poc, l0.poc, l1.poc);
let (sy, su, sv) = coded_source(cfg, frame);
let lambda = 0.85 * fe.tune_lambda_scale * 2f64.powf((qp as f64 - 12.0) / 3.0);
let lme = lambda.sqrt() * cfg.cabac_lambda_scale;
let refs = std::slice::from_ref(l0);
if fe.satd_q > 0.0 {
let mut vars: Vec<i64> = (0..fe.mb_h)
.flat_map(|my| (0..fe.mb_w).map(move |mx| (mx, my)))
.map(|(mx, my)| mb_variance(&sy, fe.cw, mx, my))
.collect();
vars.sort_unstable();
let idx = (((1.0 - fe.satd_q) * vars.len() as f64) as usize).min(vars.len() - 1);
fe.satd_var_thresh = vars[idx];
}
let mut aq_qp = aq_qp_map(&sy, fe.cw, fe.mb_w, fe.mb_h, qp, fe.aq_strength);
apply_mbtree_qpo(&mut aq_qp, qpo); fe.cur_qp = qp;
let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false);
let mut cs = CabacState::new(fe.mb_w * fe.mb_h);
let total = fe.mb_w * fe.mb_h;
for mb_y in 0..fe.mb_h {
for mb_x in 0..fe.mb_w {
let mb_idx = mb_y * fe.mb_w + mb_x;
let addr = mb_idx;
let top = if mb_y > 0 { Some(addr - fe.mb_w) } else { None };
let left = if mb_x > 0 { Some(addr - 1) } else { None };
fe.qp = aq_qp[mb_idx];
fe.qpc = chroma_qp(aq_qp[mb_idx]);
let (lx, ly) = (mb_x * 16, mb_y * 16);
let (pbx, pby) = (mb_x as isize * 4, mb_y as isize * 4);
fe.mb_use_satd =
fe.satd_q > 0.0 && mb_variance(&sy, fe.cw, mb_x, mb_y) >= fe.satd_var_thresh;
let n0 = fe.mv_neighbors_block_list(pbx, pby, 4, 0);
let n1 = fe.mv_neighbors_block_list(pbx, pby, 4, 1);
let pmv0 = predict_partition_mv(0, 0, n0[0], n0[1], n0[2], 0);
let pmv1 = predict_partition_mv(0, 0, n1[0], n1[1], n1[2], 0);
let (dp, dc, dmotion) = fe.b_direct(l0, l1, mb_x, mb_y);
if fe.skip_luma_is_free(&sy, mb_x, mb_y, &dp)
&& fe.skip_chroma_is_free(&su, &sv, mb_x, mb_y, &dc)
{
fe.commit_direct_motion(mb_x, mb_y, &dmotion);
emit_b_skip_cabac(&mut cab, &mut cs, addr, top, left);
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_terminate(mb_idx + 1 == total);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
continue;
}
let d_direct = fe.pred_dist(&sy, lx, ly, &dp);
let (mv0, j0) = fe.motion_search(l0, &sy, lx, ly, 16, 16, &[pmv0], lme, None);
let (mv1, j1) = fe.motion_search(l1, &sy, lx, ly, 16, 16, &[pmv1], lme, None);
let d_bi = fe.bi_dist(l0, l1, &sy, lx, ly, mv0, mv1);
let r_bi = mvd_bits(mv0.0 - pmv0.0) + mvd_bits(mv0.1 - pmv0.1)
+ mvd_bits(mv1.0 - pmv1.0) + mvd_bits(mv1.1 - pmv1.1);
let j_bi = d_bi + (lme * r_bi as f64) as i64;
let (mut dir, mut best) = (0u8, d_direct);
if j0 < best { dir = 1; best = j0; }
if j1 < best { dir = 2; best = j1; }
if j_bi < best { dir = 3; best = j_bi; }
let _ = best;
let sctx = 24
+ left.map_or(0, |a| (!cs.mb_skip[a]) as usize)
+ top.map_or(0, |a| (!cs.mb_skip[a]) as usize);
let tskip = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cb_mb_skip(&mut cab, sctx, false);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::SkipFlag, cab.pos() - tskip);
}
cs.mb_skip[addr] = false;
let bspec = BInter { dir, l1, mv0, mv1 };
let plan = fe.plan_inter_mb(refs, &sy, &su, &sv, mb_x, mb_y, 0, &[], Some(bspec));
emit_mb_cabac_b(&mut fe, &mut cab, &mut cs, dir, &plan, mb_x, mb_y);
{
let tt = if crate::bitacct::enabled() { cab.pos() } else { 0 };
cab.encode_terminate(mb_idx + 1 == total);
if crate::bitacct::enabled() {
crate::bitacct::add(crate::bitacct::B::Terminate, cab.pos() - tt);
}
}
}
}
while !w.is_byte_aligned() {
w.write_bit(true);
}
for b in cab.into_bytes() {
w.write_bits(b as u32, 8);
}
}
pub fn encode_all_skip_b_cabac(w: &mut BitWriter, cfg: &EncoderConfig, qp: u8, n: usize) {
let mut cab = CabacEncoder::new(qp as i32, cfg.cabac_init_idc, false);
for i in 0..n {
cab.encode_decision(24, 1);
cab.encode_terminate(i + 1 == n);
}
while !w.is_byte_aligned() {
w.write_bit(true);
}
for b in cab.into_bytes() {
w.write_bits(b as u32, 8);
}
}