#[allow(unused_imports)]
use alloc::vec::Vec;
#[allow(unused_imports)]
use rusty_h264_common::fmath::{F32Ext as _, F64Ext as _};
use core::cell::RefCell;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CabacBin {
pub ctx_idx: u16,
pub state: u8,
pub mps: u8,
pub bin: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SliceTap {
pub qp: i32,
pub init_idc: u32,
pub is_i: bool,
pub bins: Vec<CabacBin>,
}
pub fn p_zero_q8(state: u8, mps: u8) -> u8 {
use rusty_h264_common::cabac_tables::RANGE_LPS;
const MID: [f64; 4] = [288.0, 352.0, 416.0, 480.0];
let s = state.min(62) as usize;
let p_lps: f64 = (0..4).map(|q| RANGE_LPS[s][q] as f64 / MID[q]).sum::<f64>() / 4.0;
let p_zero = if mps == 0 { 1.0 - p_lps } else { p_lps };
rusty_h264_common::fmath::round(p_zero * 256.0).clamp(1.0, 255.0) as u8
}
struct TapState {
enabled: bool,
slices: Vec<SliceTap>,
}
thread_local! {
static TAP: RefCell<TapState> = const {
RefCell::new(TapState {
enabled: false,
slices: Vec::new(),
})
};
}
pub fn enable(on: bool) {
TAP.with(|t| {
let mut t = t.borrow_mut();
t.enabled = on;
if !on {
t.slices = Vec::new();
}
});
}
pub fn take() -> Vec<SliceTap> {
TAP.with(|t| core::mem::take(&mut t.borrow_mut().slices))
}
#[inline]
pub(crate) fn begin_slice(qp: i32, init_idc: u32, is_i: bool) {
TAP.with(|t| {
let mut t = t.borrow_mut();
if t.enabled {
t.slices.push(SliceTap {
qp,
init_idc,
is_i,
bins: Vec::new(),
});
}
});
}
#[inline]
pub(crate) fn record(ctx_idx: u16, state: u8, mps: u8, bin: u8) {
TAP.with(|t| {
let mut t = t.borrow_mut();
if t.enabled {
if let Some(s) = t.slices.last_mut() {
s.bins.push(CabacBin {
ctx_idx,
state,
mps,
bin,
});
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec,
vec::Vec,
};
#[allow(unused_imports)]
use rusty_h264_common::once::OnceLock;
#[test]
fn segments_and_records() {
enable(true);
begin_slice(26, 0, true);
record(105, 0, 0, 1);
record(105, 0, 1, 0);
begin_slice(28, 1, false);
record(11, 5, 1, 1);
let s = take();
enable(false);
assert_eq!(s.len(), 2);
assert_eq!(s[0].bins.len(), 2);
assert_eq!((s[0].qp, s[0].init_idc, s[0].is_i), (26, 0, true));
assert_eq!(s[1].bins[0].ctx_idx, 11);
}
#[test]
fn disabled_records_nothing() {
enable(false);
begin_slice(26, 0, true);
record(1, 0, 0, 0);
assert!(take().is_empty());
}
#[test]
fn p_zero_is_sane() {
let p = p_zero_q8(0, 0);
assert!((120..=136).contains(&p), "state0 ≈ ½, got {p}");
let p = p_zero_q8(62, 1);
assert!(p <= 8, "deep state LPS should be tiny, got {p}");
assert_eq!(
p_zero_q8(30, 0) as u32 + p_zero_q8(30, 1) as u32,
256,
"MPS flip must mirror on the grid"
);
}
}