mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 02:57:34 -06:00
221 lines
6.4 KiB
Rust
221 lines
6.4 KiB
Rust
//! CABAC: context initialisation (9.3.2.2), the arithmetic encoder (9.3.4.x, informative encoder
|
|
//! description: EncodeDecision, EncodeBypass, EncodeTerminate, EncodeFlush) and a fractional-bit
|
|
//! estimator with the same context-state evolution, used for rate-distortion decisions.
|
|
|
|
use crate::bitstream::BitWriter;
|
|
use crate::tables::{CABAC_INIT, NEXT_STATE, NUM_CTX, RANGE_TAB_LPS};
|
|
use std::sync::OnceLock;
|
|
|
|
/// Context states, each `pStateIdx << 1 | valMps`.
|
|
pub type Contexts = [u8; NUM_CTX];
|
|
|
|
/// Initialise all context variables for `slice_qp` and `init_type` (9.3.2.2).
|
|
pub fn init_contexts(slice_qp: i32, init_type: usize) -> Contexts {
|
|
let mut ctx = [0u8; NUM_CTX];
|
|
let qp = slice_qp.clamp(0, 51);
|
|
for (c, &v) in ctx.iter_mut().zip(CABAC_INIT[init_type].iter()) {
|
|
let slope = (v >> 4) as i32;
|
|
let offset = (v & 15) as i32;
|
|
let m = slope * 5 - 45;
|
|
let n = (offset << 3) - 16;
|
|
let pre = (((m * qp) >> 4) + n).clamp(1, 126);
|
|
*c = if pre <= 63 { ((63 - pre) << 1) as u8 } else { (((pre - 64) << 1) | 1) as u8 };
|
|
}
|
|
ctx
|
|
}
|
|
|
|
/// Destination of binarised syntax elements: the real arithmetic coder or the bit estimator.
|
|
pub trait Sink {
|
|
fn decision(&mut self, ctx: usize, bin: u32);
|
|
fn bypass(&mut self, bin: u32);
|
|
/// `n` bypass bins carrying the low `n` bits of `v`, MSB first.
|
|
fn bypass_bits(&mut self, v: u32, n: u32) {
|
|
for i in (0..n).rev() {
|
|
self.bypass((v >> i) & 1);
|
|
}
|
|
}
|
|
fn terminate(&mut self, bin: u32);
|
|
}
|
|
|
|
/// Arithmetic encoder writing into a bit writer.
|
|
pub struct CabacEncoder {
|
|
pub ctx: Contexts,
|
|
low: u32,
|
|
range: u32,
|
|
outstanding: u32,
|
|
first: bool,
|
|
pub w: BitWriter,
|
|
}
|
|
|
|
impl CabacEncoder {
|
|
/// Start encoding slice data into `w` (which must be byte aligned).
|
|
pub fn new(w: BitWriter, ctx: Contexts) -> Self {
|
|
debug_assert!(w.is_aligned());
|
|
CabacEncoder { ctx, low: 0, range: 510, outstanding: 0, first: true, w }
|
|
}
|
|
|
|
#[inline]
|
|
fn put_bit(&mut self, b: u32) {
|
|
if self.first {
|
|
self.first = false;
|
|
} else {
|
|
self.w.bit(b);
|
|
}
|
|
while self.outstanding > 0 {
|
|
self.w.bit(1 - b);
|
|
self.outstanding -= 1;
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn renorm(&mut self) {
|
|
while self.range < 256 {
|
|
if self.low < 256 {
|
|
self.put_bit(0);
|
|
} else if self.low >= 512 {
|
|
self.low -= 512;
|
|
self.put_bit(1);
|
|
} else {
|
|
self.low -= 256;
|
|
self.outstanding += 1;
|
|
}
|
|
self.range <<= 1;
|
|
self.low <<= 1;
|
|
}
|
|
}
|
|
|
|
/// EncodeFlush after a terminating bin equal to 1; the final bit written is the
|
|
/// rbsp_stop_one_bit. Returns the writer (byte aligned with zero bits).
|
|
pub fn finish(mut self) -> BitWriter {
|
|
self.range = 2;
|
|
self.renorm();
|
|
let b = (self.low >> 9) & 1;
|
|
self.put_bit(b);
|
|
self.w.put(((self.low >> 7) & 3) | 1, 2);
|
|
self.w.align_zero();
|
|
self.w
|
|
}
|
|
}
|
|
|
|
impl Sink for CabacEncoder {
|
|
#[inline]
|
|
fn decision(&mut self, ctx_idx: usize, bin: u32) {
|
|
let s = self.ctx[ctx_idx] as usize;
|
|
let mps = (s & 1) as u32;
|
|
let q = ((self.range >> 6) & 3) as usize;
|
|
let lps = RANGE_TAB_LPS[s >> 1][q] as u32;
|
|
self.range -= lps;
|
|
if bin != mps {
|
|
self.low += self.range;
|
|
self.range = lps;
|
|
self.ctx[ctx_idx] = NEXT_STATE[s][1];
|
|
} else {
|
|
self.ctx[ctx_idx] = NEXT_STATE[s][0];
|
|
}
|
|
self.renorm();
|
|
}
|
|
|
|
#[inline]
|
|
fn bypass(&mut self, bin: u32) {
|
|
self.low <<= 1;
|
|
if bin != 0 {
|
|
self.low += self.range;
|
|
}
|
|
if self.low >= 1024 {
|
|
self.put_bit(1);
|
|
self.low -= 1024;
|
|
} else if self.low < 512 {
|
|
self.put_bit(0);
|
|
} else {
|
|
self.low -= 512;
|
|
self.outstanding += 1;
|
|
}
|
|
}
|
|
|
|
fn terminate(&mut self, bin: u32) {
|
|
self.range -= 2;
|
|
if bin != 0 {
|
|
self.low += self.range;
|
|
// the caller flushes with finish()
|
|
} else {
|
|
self.renorm();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Fractional bit cost (1/32768 bit units) of coding the MPS / LPS in each state.
|
|
fn cost_table() -> &'static [[u32; 2]; 64] {
|
|
static T: OnceLock<[[u32; 2]; 64]> = OnceLock::new();
|
|
T.get_or_init(|| {
|
|
let mut t = [[0u32; 2]; 64];
|
|
// pLPS(s) = 0.5 * alpha^s with alpha = (0.01875 / 0.5)^(1/63) (9.3.4.3 design)
|
|
let alpha = (0.01875f64 / 0.5).powf(1.0 / 63.0);
|
|
for (s, e) in t.iter_mut().enumerate() {
|
|
let p = 0.5 * alpha.powi(s as i32);
|
|
e[0] = (-(1.0 - p).log2() * 32768.0).round() as u32;
|
|
e[1] = (-p.log2() * 32768.0).round() as u32;
|
|
}
|
|
t
|
|
})
|
|
}
|
|
|
|
pub const BIT: u64 = 32768;
|
|
|
|
/// Bit estimator: tracks context states like the encoder and accumulates the ideal code length.
|
|
#[derive(Clone)]
|
|
pub struct Estimator {
|
|
pub ctx: Contexts,
|
|
/// Accumulated cost in 1/32768 bits.
|
|
pub bits: u64,
|
|
table: &'static [[u32; 2]; 64],
|
|
}
|
|
|
|
impl Estimator {
|
|
pub fn new(ctx: Contexts) -> Self {
|
|
Estimator { ctx, bits: 0, table: cost_table() }
|
|
}
|
|
}
|
|
|
|
impl Sink for Estimator {
|
|
#[inline]
|
|
fn decision(&mut self, ctx_idx: usize, bin: u32) {
|
|
let s = self.ctx[ctx_idx] as usize;
|
|
let lps = (bin != (s & 1) as u32) as usize;
|
|
self.bits += self.table[s >> 1][lps] as u64;
|
|
self.ctx[ctx_idx] = NEXT_STATE[s][lps];
|
|
}
|
|
#[inline]
|
|
fn bypass(&mut self, _bin: u32) {
|
|
self.bits += BIT;
|
|
}
|
|
#[inline]
|
|
fn bypass_bits(&mut self, _v: u32, n: u32) {
|
|
self.bits += BIT * n as u64;
|
|
}
|
|
fn terminate(&mut self, _bin: u32) {
|
|
self.bits += 7; // ~0 bits for a 0 bin
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn context_init_formula() {
|
|
let ctx = init_contexts(30, 0);
|
|
// initValue 154: m = 0, n = 64 -> pStateIdx 0, valMps 1
|
|
assert_eq!(ctx[crate::tables::CU_TRANSQUANT_BYPASS], 1);
|
|
// split_cu_flag ctx 0 (initValue 139) at QP 30: preCtxState 62
|
|
assert_eq!(ctx[crate::tables::SPLIT_CU], (63 - 62) << 1);
|
|
}
|
|
|
|
#[test]
|
|
fn costs_are_sane() {
|
|
let t = cost_table();
|
|
assert_eq!(t[0][0], 32768);
|
|
assert_eq!(t[0][1], 32768);
|
|
assert!(t[62][0] < 1000 && t[62][1] > 5 * 32768);
|
|
}
|
|
}
|