mod aq;
mod avif;
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
mod avx;
mod ccso;
mod cdef_est;
mod deblock;
mod cdf_para;
mod cdf_state;
mod cdfs_qctx;
pub(crate) mod cdfs_uv_qcx;
mod cdfx_4tx;
#[allow(dead_code)]
mod cfl;
mod chroma422;
mod coder;
mod csc;
#[allow(dead_code)]
mod directional;
mod entropy;
mod fdct;
mod headers;
pub(crate) mod helpers;
mod intrapred;
pub(crate) mod itx;
pub mod itx422;
mod layout;
mod leaf;
mod lossless;
mod lossless_rd;
mod metrics;
mod mhccp;
#[cfg(all(target_arch = "aarch64", feature = "neon"))]
mod neon;
mod partition;
mod proj;
mod quant;
#[allow(dead_code)]
mod replay;
pub mod simple;
pub(crate) mod tables;
mod tables_tx32;
mod tiling;
pub mod video;
mod wht;
mod y400;
mod y420;
mod y422;
mod y444;
use crate::av2::avif::{Av2Color, Av2Format};
use crate::av2::cdfs_qctx::{
CHROMA_EOB_HI_BIT_QC, CHROMA_SKIP_TX32_QC, CHROMA_SKIP_TX64_QC, INTER_SKIP_TX16_QC,
SKIP_TX8_QC, SKIP_TX16_QC,
};
use crate::av2::cdfx_4tx::{TXB_SKIP_TX4_Q0, V_TXB_SKIP_TX4_Q0};
use crate::av2::chroma422::{
ChromaNeighbors, ChromaPlanes, ChromaTuInput, ChromaTxSpec, code_422_chroma_tu,
recon_422_chroma,
};
use crate::av2::coder::{
Coeff, EobCdf, InterResidualSpec, LosslessDpcm, LosslessIntrabc, LosslessLumaBlock,
LumaLeafRect128Spec, LumaSplitDirSpec, encode_chroma_block, encode_chroma_block_ex,
encode_chroma_block_rect, encode_chroma_block_rect_w, encode_chroma_tu4,
encode_lossless_luma_sb, encode_luma_block_horz4, encode_luma_block_split,
encode_luma_block_split_dir, encode_luma_block_vert4, encode_luma_leaf_8x8,
encode_luma_leaf_8x32, encode_luma_leaf_16x16_full, encode_luma_leaf_16x32,
encode_luma_leaf_16x64, encode_luma_leaf_32x8, encode_luma_leaf_32x16, encode_luma_leaf_32x32,
encode_luma_leaf_32x64, encode_luma_leaf_64x16, encode_luma_leaf_64x32,
};
use crate::av2::csc::{
CB_B, CB_G, CB_R, CR_B, CR_G, CR_R, HALF, Q, Y_B, Y_G, Y_R, get_q_ctx, validate_dims,
};
use crate::av2::entropy::RangeEncoder;
use crate::av2::headers::{Config, frame_header, obu, sequence_header};
use crate::av2::helpers::{
BoundedIntraRect, PlaneRect, TX16_TYPE_RATE_DELTA, TxbContextSpec, choose_tx16_type,
coeff_abs_rate_f32, coeff_rate_f32, coeff_tu_rate_proxy_f32, coeff_tus_rate_proxy_f32, dc_pred,
dc_pred_rect, dc_pred_rect_bounded, dc_pred_rect_subsampled, get_residual, get_residual_rect,
levels_to_coeffs, lossless_sb_tus, pad_plane, par_map_indexed, pixel_sse_f32_u16_block,
pixel_sse_rounded_block, pixel_sse_rounded_block_const, put_block, put_block_rect, rect_rows,
rect_rows_mut, rect_sse_f32, sb_align, sb_tu_contexts, sb_tu_contexts_64x32,
sb_tu_contexts_pos, sb_tu_contexts_rect, sb_tu4_chroma_skip, sb_tu4_contexts, tx16_dc_only,
tx16_distortion,
};
use crate::av2::itx422::reconstruct_luma;
use crate::av2::layout::Layout;
use crate::av2::leaf::{
LumaFrameBlock, LumaGridBlock, LumaPartitionDecision, LumaPartitionSearch, LumaQuantSpec,
LumaSbSearch, LumaSource, choose_luma_64x64_partition, encode_luma_leaf_s32x32,
encode_luma_leaf_v32x64, encode_luma_leaf32, encode_luma_sb,
};
use crate::av2::proj::Basis;
use crate::av2::tables::{SCAN8X8, SCAN8X32, SCAN16, SCAN16X32, SCAN32X8, SCAN32X16};
use crate::err::EncodeError;
use crate::metadata::{ContentLightLevel, Orientation};
use crate::{ChromaFormat, Cicp, Pixel, PlanarImage, Speed};
pub use aq::DarkAq;
fn split_one_obu(data: &[u8]) -> (&[u8], &[u8]) {
let mut size = 0u64;
let mut shift = 0;
let mut i = 0;
loop {
let b = data[i];
size |= ((b & 0x7f) as u64) << shift;
i += 1;
if b & 0x80 == 0 {
break;
}
shift += 7;
}
let total = i + size as usize;
(&data[..total], &data[total..])
}
pub struct Av2Frame {
data: Vec<u8>,
width: usize,
height: usize,
coded_width: usize,
coded_height: usize,
bit_depth: u8,
color: Cicp,
chroma_format: ChromaFormat,
pub(crate) tile_grid: (usize, usize, usize),
pub(crate) recon: Vec<Vec<f32>>,
pub(crate) allow_intrabc: bool,
pub(crate) video_config: Config,
}
impl Av2Frame {
pub fn view(&self) -> &[u8] {
self.data.as_slice()
}
pub fn reconstruction(&self) -> &[Vec<f32>] {
&self.recon
}
pub(crate) fn coded_dims(&self) -> (usize, usize) {
(self.coded_width, self.coded_height)
}
pub(crate) fn tile_grid(&self) -> (usize, usize, usize) {
self.tile_grid
}
pub(crate) fn recon_planes(&self) -> &[Vec<f32>] {
&self.recon
}
pub(crate) fn allow_intrabc(&self) -> bool {
self.allow_intrabc
}
pub(crate) fn video_config(&self) -> &Config {
&self.video_config
}
pub(crate) fn split_obus(&self) -> (&[u8], &[u8], &[u8]) {
let (td, rest) = split_one_obu(&self.data);
let (seq, frame) = split_one_obu(rest);
(td, seq, frame)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TxPart {
#[default]
ThreeWay,
Rd2,
Split,
Vert4,
Horz4,
}
#[derive(Clone, Copy, Debug)]
pub struct Tuning {
pub tile_cols: usize,
pub tile_rows: usize,
pub txpart: TxPart,
pub rdoq_lambda: f32,
pub chroma_rdoq_lambda: f32,
pub part_lambda_c: f32,
pub deblock: bool,
pub chroma_deblock: bool,
pub ccso: bool,
pub ccso_rd_scale: f32,
pub db_delta_y: i32,
pub db_delta_uv: i32,
pub cdef: bool,
pub cfl: bool,
pub mhccp: bool,
pub aq: bool,
pub vb_octile: u8,
pub vb_strength: f32,
pub vb_boost_only: bool,
pub dark_aq: aq::DarkAq,
pub chroma_mode_search: bool,
pub chroma_split: bool,
pub updating_cdf: bool,
pub uv_ac_delta_q: i32,
pub rect_part_penalty: f32,
}
impl Default for Tuning {
fn default() -> Self {
Tuning {
tile_cols: 1,
tile_rows: 1,
txpart: TxPart::ThreeWay,
rdoq_lambda: proj::DEFAULT_RDOQ_LAMBDA,
chroma_rdoq_lambda: proj::DEFAULT_RDOQ_LAMBDA,
part_lambda_c: 0.0001,
deblock: true,
chroma_deblock: true,
ccso: false,
ccso_rd_scale: 1.0,
db_delta_y: i32::MIN,
db_delta_uv: 0,
cdef: false,
cfl: true,
mhccp: true,
aq: true,
vb_octile: 6,
vb_strength: 0.6,
vb_boost_only: true,
dark_aq: aq::DarkAq {
enabled: true,
..aq::DarkAq::default()
},
chroma_mode_search: true,
chroma_split: true,
uv_ac_delta_q: 0,
updating_cdf: true,
rect_part_penalty: 1.05,
}
}
}
pub struct Av2Encoder {
bases: proj::Bases,
base_q_idx: u8,
bit_depth: u8,
tune: Tuning,
speed: Speed,
pub(crate) video_search_range: i32,
pub(crate) video_predictor_gate: u32,
pub(crate) video_integer_satd_radius: u8,
pub(crate) video_min_block_size: u8,
pub(crate) video_max_partition_depth: u8,
pub(crate) video_mv_seed: std::sync::Mutex<video::mv::Mv>,
threads: usize,
pub(crate) inter_tile: std::sync::atomic::AtomicBool,
pub(crate) video_mode: std::sync::atomic::AtomicBool,
pub(crate) capture_recon: std::sync::atomic::AtomicBool,
pub(crate) last_ref: std::sync::Mutex<std::sync::Arc<Vec<Vec<f32>>>>,
pub(crate) second_ref: std::sync::Mutex<std::sync::Arc<Vec<Vec<f32>>>>,
pub(crate) ref_dists: std::sync::Mutex<[i32; 2]>,
}
fn lossy_native_mi(width: usize, height: usize) -> Option<(i64, i64)> {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let ok = |m: i64| m % 16 == 0 || m % 16 >= 6 || m % 16 == 4 || m % 16 == 2;
if !(ok(mc) && ok(mr)) {
return None;
}
Some((mc, mr))
}
fn lossy_needs_partition(width: usize, height: usize) -> bool {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let part = |m: i64| m % 16 == 6 || m % 16 == 8 || m % 16 == 4 || m % 16 == 2;
part(mc) || part(mr)
}
fn native_420_mi(width: usize, height: usize) -> Option<(i64, i64)> {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let ok = |m: i64| m % 16 == 0 || m % 16 >= 6 || m % 16 == 4 || m % 16 == 2;
if !(ok(mc) && ok(mr)) {
return None;
}
Some((mc, mr))
}
fn native_422_mi(width: usize, height: usize) -> Option<(i64, i64)> {
lossy_native_mi(width, height)
}
#[derive(Clone, Copy)]
struct QuantCtx {
qc: usize,
neutral: f32,
qstep: i32,
rdoq_lambda: f32,
}
#[derive(Clone, Copy)]
struct PartitionPass {
luma_stride: usize,
chroma_stride: usize,
width: usize,
height: usize,
sb_rows: usize,
sb_cols: usize,
tmc: i64,
tmr: i64,
quant: QuantCtx,
}
struct LumaPlanes<'a> {
rec: &'a mut [f32],
src: &'a [f32],
}
struct ChromaPlaneRefs<'a> {
rec_u: &'a mut [f32],
rec_v: &'a mut [f32],
src_u: &'a [f32],
src_v: &'a [f32],
}
struct PartitionNeighbors<'a> {
above: &'a mut [u8],
left: &'a mut [u8],
above_pctx: &'a mut [u8],
left_pctx: &'a mut [u8],
}
struct ChromaNeighborBufs<'a> {
u_above: &'a mut [i32],
v_above: &'a mut [i32],
u_left: &'a mut [i32],
v_left: &'a mut [i32],
}
impl Av2Encoder {
pub fn with_reconstruction(self, retain: bool) -> Self {
self.capture_recon
.store(retain, std::sync::atomic::Ordering::Relaxed);
self
}
pub fn new(base_q_idx: u8) -> Self {
Self::with_bit_depth(base_q_idx, 8)
}
pub fn with_bit_depth(base_q_idx: u8, bit_depth: u8) -> Self {
assert!(
matches!(bit_depth, 8 | 10 | 12),
"bit_depth must be 8, 10 or 12, got {bit_depth}"
);
let mut bases = proj::default_bases().rescaled_to_q(base_q_idx as u32);
bases.set_bit_depth(bit_depth);
let mut enc = Av2Encoder {
bases,
base_q_idx,
bit_depth,
tune: Tuning::default(),
speed: Speed::Slow,
video_search_range: 128,
video_predictor_gate: 0,
video_integer_satd_radius: 0,
video_min_block_size: 8,
video_max_partition_depth: 4,
video_mv_seed: std::sync::Mutex::new(video::mv::Mv::ZERO),
threads: 1,
inter_tile: std::sync::atomic::AtomicBool::new(false),
video_mode: std::sync::atomic::AtomicBool::new(false),
capture_recon: std::sync::atomic::AtomicBool::new(false),
last_ref: std::sync::Mutex::new(std::sync::Arc::new(Vec::new())),
second_ref: std::sync::Mutex::new(std::sync::Arc::new(Vec::new())),
ref_dists: std::sync::Mutex::new([1, 1]),
};
enc.apply_hq_lambda_schedule();
enc
}
pub fn with_threads(mut self, threads: usize) -> Self {
self.threads = threads;
self
}
pub fn with_tuning(mut self, tune: Tuning) -> Self {
self.tune = tune;
self.apply_hq_lambda_schedule();
self
}
pub(crate) fn apply_hq_lambda_schedule(&mut self) {
let sched = proj::hq_rdoq_lambda(self.base_q_idx as u32);
if (self.tune.rdoq_lambda - proj::DEFAULT_RDOQ_LAMBDA).abs() < 1e-12 {
self.tune.rdoq_lambda = sched;
}
if (self.tune.chroma_rdoq_lambda - proj::DEFAULT_RDOQ_LAMBDA).abs() < 1e-12 {
self.tune.chroma_rdoq_lambda = sched;
}
}
#[inline]
pub(crate) fn video_allows_16x16_partitions(&self) -> bool {
self.video_min_block_size <= 16 && self.video_max_partition_depth >= 2
}
pub fn with_tiles(mut self, cols: usize, rows: usize) -> Self {
self.tune.tile_cols = cols.max(1);
self.tune.tile_rows = rows.max(1);
self
}
pub fn with_txpart(mut self, txpart: TxPart) -> Self {
self.tune.txpart = txpart;
self
}
pub fn with_rdoq_lambda(mut self, lambda: f32) -> Self {
self.tune.rdoq_lambda = lambda;
self
}
pub fn with_chroma_rdoq_lambda(mut self, lambda: f32) -> Self {
self.tune.chroma_rdoq_lambda = lambda.max(0.0);
self
}
pub fn with_speed(mut self, speed: Speed) -> Self {
self.speed = speed;
self
}
pub fn with_deblock(mut self, on: bool) -> Self {
self.tune.deblock = on;
self
}
pub fn with_cdef(mut self, on: bool) -> Self {
self.tune.cdef = on;
self
}
pub fn with_cfl(mut self, on: bool) -> Self {
self.tune.cfl = on;
self
}
pub fn with_aq(mut self, on: bool) -> Self {
self.tune.aq = on;
self
}
pub fn with_variance_boost(mut self, octile: u8, strength: f32, boost_only: bool) -> Self {
self.tune.vb_octile = octile.clamp(1, 8);
self.tune.vb_strength = strength.max(0.0);
self.tune.vb_boost_only = boost_only;
self
}
pub fn with_chroma_mode_search(mut self, on: bool) -> Self {
self.tune.chroma_mode_search = on;
self
}
pub fn with_ccso(mut self, on: bool) -> Self {
self.tune.ccso = on;
self
}
pub fn with_chroma_split(mut self, on: bool) -> Self {
self.tune.chroma_split = on;
self
}
pub fn with_mhccp(mut self, on: bool) -> Self {
self.tune.mhccp = on;
self
}
pub fn with_updating_cdf(mut self, on: bool) -> Self {
self.tune.updating_cdf = on;
self
}
pub fn tuning(&self) -> Tuning {
self.tune
}
pub fn base_q_idx(&self) -> u8 {
self.base_q_idx
}
pub(crate) fn set_video_base_q(&mut self, base_q_idx: u8) {
if self.base_q_idx == base_q_idx {
return;
}
self.base_q_idx = base_q_idx;
self.bases = proj::default_bases().rescaled_to_q(base_q_idx as u32);
self.bases.set_bit_depth(self.bit_depth);
}
fn config(&self, layout: Layout) -> Config {
let df_quant = {
let qs = crate::av2::quant::qstep(self.base_q_idx as u32) as i32;
((qs + 4) >> 3) >> 6
};
let filter_active = self.tune.deblock && df_quant >= 1;
let auto_dy = if df_quant >= 5 { 0 } else { 1 };
let eff_dy = if self.tune.db_delta_y == i32::MIN {
auto_dy
} else {
self.tune.db_delta_y
};
Config {
layout,
base_q: self.base_q_idx as u32,
uv_ac_delta_q: if layout == Layout::I444 {
self.tune.uv_ac_delta_q
} else {
0
},
deblock: filter_active,
db_apply: (
filter_active,
filter_active,
filter_active && self.tune.chroma_deblock,
filter_active && self.tune.chroma_deblock,
),
db_delta: (eff_dy, eff_dy, self.tune.db_delta_uv, self.tune.db_delta_uv),
tx_switchable: true,
guided_deblock: None,
cdef: None,
cdef_per_block: false,
ccso: None,
bit_depth: self.bit_depth,
lossless: self.base_q_idx == 0,
allow_intrabc: false,
cfl: self.tune.cfl && self.base_q_idx != 0,
mhccp: self.tune.mhccp && self.base_q_idx != 0 && layout.has_chroma(),
aq: self.tune.aq && self.base_q_idx != 0,
aq_res_log2: 2,
updating_cdf: self.tune.updating_cdf && self.base_q_idx != 0,
}
}
fn dc_neutral(&self) -> f32 {
(1u32 << (self.bit_depth - 1)) as f32
}
pub(crate) fn config_for(&self, layout: Layout) -> Config {
self.config(layout)
}
fn resolve_threads(threads: usize) -> usize {
if threads == 0 {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
} else {
threads
}
}
#[allow(clippy::too_many_arguments)]
fn finish(
&self,
mut enc: RangeEncoder,
config: &Config,
pw: usize,
ph: usize,
width: usize,
height: usize,
color: &Cicp,
) -> Av2Frame {
let ccso_u_result = enc.ccso_u_result.clone();
let ccso_v_result = enc.ccso_v_result.clone();
let cdef_result = enc.cdef_result;
let cdef_decided = enc.cdef_decided.clone();
let recon = std::mem::take(&mut enc.recon);
let tile = enc.finish();
let mi_cols = ((width + 7) & !7) / 4;
let mi_rows = ((height + 7) & !7) / 4;
const MIB: usize = 16; let aligned = mi_cols.is_multiple_of(MIB) && mi_rows.is_multiple_of(MIB);
let lossy_native = !config.lossless
&& ((config.layout == Layout::I444 && lossy_native_mi(width, height).is_some())
|| (config.layout == Layout::I422 && native_422_mi(width, height).is_some())
|| (config.layout == Layout::I420 && native_420_mi(width, height).is_some())
|| (config.layout == Layout::Monochrome
&& lossy_native_mi(width, height).is_some()));
let exact = config.lossless || aligned || lossy_native;
let (sw, sh) = if exact { (width, height) } else { (pw, ph) };
let mut config = (*config).clone();
if self.tune.cdef {
if let Some(d) = &cdef_decided {
config.cdef = Some((d.y_str, d.uv_str, d.damping));
config.cdef_per_block = true;
} else {
config.cdef = cdef_result;
}
}
let to_plane = |r: &ccso::PlaneResult| -> headers::CcsoPlane {
use crate::av2::ccso::PlaneResult::*;
match r {
Edge {
scale_idx,
quant_idx,
ext_filter_support,
edge_clf,
max_band_log2,
offsets,
} => headers::CcsoPlane {
bo_only: false,
scale_idx: *scale_idx,
quant_idx: *quant_idx,
ext_filter_support: *ext_filter_support,
edge_clf: *edge_clf,
max_band_log2: *max_band_log2,
offsets: offsets.clone(),
},
}
};
if ccso_u_result.is_some() || ccso_v_result.is_some() {
config.ccso = Some(headers::CcsoConfig {
enable: [false, ccso_u_result.is_some(), ccso_v_result.is_some()],
planes: [
None,
ccso_u_result.as_ref().map(&to_plane),
ccso_v_result.as_ref().map(&to_plane),
],
});
}
let config = &config;
let mut frame = frame_header(config, sw as u32, sh as u32, (0, 0, 1));
frame.extend(&tile);
let mut data = vec![];
data.extend(obu(2, &[]));
data.extend(obu(1, &sequence_header(config, sw as u32, sh as u32)));
data.extend(obu(4, &frame));
Av2Frame {
data,
width,
height,
coded_width: sw,
coded_height: sh,
bit_depth: self.bit_depth,
color: *color,
recon,
allow_intrabc: config.allow_intrabc,
video_config: config.clone(),
tile_grid: (0, 0, 1),
chroma_format: match config.layout {
Layout::Monochrome => ChromaFormat::Monochrome,
Layout::I420 => ChromaFormat::Yuv420,
Layout::I422 => ChromaFormat::Yuv422,
Layout::I444 => ChromaFormat::Yuv444,
},
}
}
pub fn wrap_avif(
frame: &Av2Frame,
icc_profile: Option<&[u8]>,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Result<Vec<u8>, EncodeError> {
let format = Av2Format {
bit_depth: frame.bit_depth,
monochrome: frame.chroma_format == ChromaFormat::Monochrome,
chroma_sub_x: frame.chroma_format == ChromaFormat::Yuv422
|| frame.chroma_format == ChromaFormat::Yuv420,
chroma_sub_y: frame.chroma_format == ChromaFormat::Yuv420,
};
let color = match icc_profile {
Some(icc) => Av2Color::Both {
cicp: frame.color,
icc: icc.to_vec(),
},
None => Av2Color::Cicp(frame.color),
};
Ok(avif::to_avif_color(
frame,
&format,
&color,
exif,
orientation,
clli,
))
}
pub fn wrap_avif_alpha(
frame: &Av2Frame,
alpha: &Av2Frame,
icc_profile: Option<&[u8]>,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Result<Vec<u8>, EncodeError> {
let format = Av2Format {
bit_depth: frame.bit_depth,
monochrome: frame.chroma_format == ChromaFormat::Monochrome,
chroma_sub_x: frame.chroma_format == ChromaFormat::Yuv422
|| frame.chroma_format == ChromaFormat::Yuv420,
chroma_sub_y: frame.chroma_format == ChromaFormat::Yuv420,
};
let color = match icc_profile {
Some(icc) => Av2Color::Both {
cicp: frame.color,
icc: icc.to_vec(),
},
None => Av2Color::Cicp(frame.color),
};
Ok(avif::to_avif_color_alpha(
frame,
alpha,
&format,
&color,
exif,
orientation,
clli,
))
}
}
pub fn av2_map_quality(quality: u8) -> u8 {
debug_assert!((1..=100).contains(&quality));
((100 - quality as u32) * 254 / 99).clamp(1, 254) as u8
}