mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-29 04:56:58 -06:00
- 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>
171 lines
6.5 KiB
Rust
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);
|
|
}
|
|
}
|