Files
deckcraft/crates/hevc/src/transform.rs
T
Brandon ThomasandClaude Opus 5.5 4375fe7b89 Media playback: slidecraft-media (probe, audio decode, H.264/HEVC/VP9/AV1 video, mixer and clock), cpal output, editor control bar, slide show playback, media.* commands
- crates/media: probe (duration, rate/channels, size/fps; WMA/WMV recognised), audio decode to
  f32 PCM (MP3, AAC M4A/ADTS, ALAC, WAV, AIFF, CAF, FLAC, Vorbis via symphonia; Opus with our
  decoder in Ogg/WebM/MKV), video decode (MP4/MOV/WebM/MKV) with YUV->RGBA, poster frames,
  trim/fade/volume, Player mixer + per-voice clock (device-clocked or wall-clocked), VideoFeed.
- Codec crates ported from FilmCraft: bitstream, h264, hevc, vp9, av1, isobmff, matroska, ogg, opus.
- insert.audio/insert.video probe and store duration/size and a poster frame; unplayable media
  is embedded with a warning. New media.play/pause/toggle/stop/seek/info/posterFrame.
- UI: MediaHost (cpal output injected by the desktop app), control bar under media, video frames
  on canvas and in the show; autoplay, click to play, loop, rewind, across slides, hide, full screen.
- Tests with in-code WAV/AIFF and CC0 self-generated fixtures (make.sh, ATTRIBUTION.md rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:35:09 -04:00

171 lines
6.5 KiB
Rust

//! Scaling (8.6.3), transform skip and inverse transforms (8.6.4): DCT 4..32 and DST 4x4.
use crate::tables::{DST_MATRIX, TRANS_MATRIX};
/// Inverse transform of an n x n block of scaled coefficients `c` (row-major, `c[y * n + x]`) in place,
/// producing residuals after the final `bd_shift` rounding (8-299). `max_x` / `max_y` bound the
/// non-zero coefficients (inclusive). `dst`: DST-VII (intra 4x4 luma).
///
/// Evaluated as sums of basis rows scaled by the non-zero coefficients (zero coefficients, which
/// are most of them, cost nothing), with the size a compile-time constant so the inner loops over
/// contiguous basis rows vectorise. Integer sums are exact (|sum| < 2^27), so the result equals the
/// direct matrix product of 8.6.4.2 in any summation order.
pub fn inverse_transform(c: &mut [i32], n: usize, dst: bool, max_x: usize, max_y: usize, bd_shift: u32) {
match n {
4 => inverse::<4>(c, dst, max_x, max_y, bd_shift),
8 => inverse::<8>(c, false, max_x, max_y, bd_shift),
16 => inverse::<16>(c, false, max_x, max_y, bd_shift),
_ => inverse::<32>(c, false, max_x, max_y, bd_shift),
}
}
#[inline(always)]
fn inverse<const N: usize>(c: &mut [i32], dst: bool, max_x: usize, max_y: usize, bd_shift: u32) {
let step = 32 / N;
// basis function of coefficient index j (row j of the transform matrix, N samples)
let basis = |j: usize| -> &[i8] { if dst { &DST_MATRIX[j][..N] } else { &TRANS_MATRIX[j * step][..N] } };
// 1. columns (8-297 ... first stage): e[x][y] = sum_j M[j][y] * d[x][j], stored transposed
// (col[x][y]) so each coefficient adds one contiguous basis row.
let mut col = [[0i32; N]; N];
for x in 0..=max_x.min(N - 1) {
let acc = &mut col[x];
for j in 0..=max_y.min(N - 1) {
let d = c[j * N + x];
if d != 0 {
for (a, &m) in acc.iter_mut().zip(basis(j)) {
*a += m as i32 * d;
}
}
}
for a in acc.iter_mut() {
*a = ((*a + 64) >> 7).clamp(-32768, 32767);
}
}
// 2. rows: r[x][y] = sum_j M[j][x] * g[j][y], g[j][y] = col[j][y]
let round = 1 << (bd_shift - 1);
for y in 0..N {
let mut out = [round; N];
for (j, g) in col.iter().enumerate().take(max_x.min(N - 1) + 1) {
let g = g[y];
if g != 0 {
for (o, &m) in out.iter_mut().zip(basis(j)) {
*o += m as i32 * g;
}
}
}
for (r, o) in c[y * N..y * N + N].iter_mut().zip(out) {
*r = o >> bd_shift;
}
}
}
/// DC-only inverse DCT: every residual sample gets the same value.
pub fn inverse_dc(dc: i32, bd_shift: u32) -> i32 {
let e = ((64 * dc + 64) >> 7).clamp(-32768, 32767);
(64 * e + (1 << (bd_shift - 1))) >> bd_shift
}
/// Transform skip residual (8-298, 8-299): r = (d << tsShift + round) >> bdShift.
pub fn transform_skip(c: &mut [i32], n: usize, bd_shift: u32) {
let ts_shift = 5 + n.trailing_zeros();
let round = 1 << (bd_shift - 1);
for v in c[..n * n].iter_mut() {
*v = ((*v << ts_shift) + round) >> bd_shift;
}
}
#[cfg(test)]
mod tests {
use super::*;
/// The direct matrix product of 8.6.4.2 (the original implementation), as the reference.
fn reference(c: &mut [i32], n: usize, dst: bool, max_x: usize, max_y: usize, bd_shift: u32) {
let mut tmp = [0i32; 32 * 32];
let step = 32 / n;
for x in 0..=max_x {
for y in 0..n {
let mut s = 0i32;
for j in 0..=max_y {
let m = if dst { DST_MATRIX[j][y] as i32 } else { TRANS_MATRIX[j * step][y] as i32 };
s += m * c[j * n + x];
}
tmp[y * n + x] = ((s + 64) >> 7).clamp(-32768, 32767);
}
}
let round = 1 << (bd_shift - 1);
for y in 0..n {
for x in 0..n {
let mut s = 0i32;
for j in 0..=max_x {
let m = if dst { DST_MATRIX[j][x] as i32 } else { TRANS_MATRIX[j * step][x] as i32 };
s += m * tmp[y * n + j];
}
c[y * n + x] = (s + round) >> bd_shift;
}
}
}
#[test]
fn matches_the_direct_matrix_product() {
// sparse and dense blocks, extreme coefficients, every size, DST, 8- and 10-bit shifts
let mut rng = 0x2545_f491_4f6c_dd1du64;
let mut next = move || {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
rng
};
for &(n, dst) in &[(4usize, true), (4, false), (8, false), (16, false), (32, false)] {
for case in 0..400 {
let (max_x, max_y) = ((next() as usize) % n, (next() as usize) % n);
let density = [2u64, 8, 50, 100][case % 4];
let mut c = vec![0i32; n * n];
for y in 0..=max_y {
for x in 0..=max_x {
if next() % 100 < density {
c[y * n + x] = match next() % 8 {
0 => 32767,
1 => -32768,
_ => (next() % 2001) as i32 - 1000,
};
}
}
}
for bd_shift in [12u32, 10] {
let (mut a, mut b) = (c.clone(), c.clone());
inverse_transform(&mut a, n, dst, max_x, max_y, bd_shift);
reference(&mut b, n, dst, max_x, max_y, bd_shift);
assert_eq!(a, b, "n {n} dst {dst} max ({max_x}, {max_y}) shift {bd_shift}");
}
}
}
}
#[test]
fn dc_only_matches_full_transform() {
for &n in &[4usize, 8, 16, 32] {
for dc in [-300, -1, 1, 7, 64, 1000] {
let mut c = vec![0i32; n * n];
c[0] = dc;
inverse_transform(&mut c, n, false, 0, 0, 12);
let v = inverse_dc(dc, 12);
assert!(c.iter().all(|&x| x == v), "n {n} dc {dc}");
}
}
}
#[test]
fn bounded_region_matches_full() {
let n = 16;
let mut c = vec![0i32; n * n];
c[0] = 50;
c[1] = -20;
c[2 * n + 3] = 9;
let mut a = c.clone();
inverse_transform(&mut a, n, false, 3, 2, 12);
let mut b = c.clone();
inverse_transform(&mut b, n, false, n - 1, n - 1, 12);
assert_eq!(a, b);
}
}