M9.7: AV1 headers, symbol decoder, CDFs, intra decoding, transforms, intra prediction
New L0 crate filmcraft-av1 from the AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1 (tables extracted from the spec's Markdown source by tools/extract_tables.py). Stages: OBU parsing, sequence and uncompressed frame headers (all fields), reference state (set_frame_refs, load_previous, reference update, show_existing_frame), symbol decoder with CDF adaptation and init/load/save/frame-end CDF handling, tiles, intra frame mode info (partitions, segment ids, delta q/lf, CDEF index, modes, angle deltas, CfL, palette, filter intra, tx size), coefficient decoding, dequantisation, inverse transforms (DCT 4-64, ADST, identity, WHT), intra prediction (all modes, edge filter/upsampling, recursive filter, CfL, palette), LR syntax. All-intra libsvtav1 streams with in-loop filters disabled decode bit-exactly against libdav1d (8/10-bit, 128x128..1280x720, odd sizes, presets 2-8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
226c139741
commit
1813bbaca2
|
|
@ -1352,6 +1352,16 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filmcraft-av1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"filmcraft-bitstream",
|
||||
"filmcraft-testkit",
|
||||
"rayon",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filmcraft-bitstream"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ filmcraft-hevc = { path = "crates/hevc" }
|
|||
filmcraft-vp9 = { path = "crates/vp9" }
|
||||
filmcraft-prores = { path = "crates/prores" }
|
||||
filmcraft-dnx = { path = "crates/dnx" }
|
||||
filmcraft-av1 = { path = "crates/av1" }
|
||||
filmcraft-aac = { path = "crates/aac" }
|
||||
filmcraft-opus = { path = "crates/opus" }
|
||||
filmcraft-h264enc = { path = "crates/h264enc" }
|
||||
|
|
@ -91,6 +92,9 @@ opt-level = 3
|
|||
[profile.dev.package.filmcraft-dnx]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-av1]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-aac]
|
||||
opt-level = 3
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "filmcraft-av1"
|
||||
description = "Clean-room AV1 decoder (AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["threads"]
|
||||
threads = ["dep:rayon"]
|
||||
|
||||
[dependencies]
|
||||
filmcraft-bitstream = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
filmcraft-testkit = { workspace = true }
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# filmcraft-av1
|
||||
|
||||
Clean-room, pure-Rust (no `unsafe`) AV1 decoder, implemented from the public **AV1 Bitstream &
|
||||
Decoding Process Specification, version 1.0.0 with Errata 1** (AOMediaCodec/av1-spec, git
|
||||
commit `5e04f3f`, 2023-06-12). It is an L0 crate: it depends only on `filmcraft-bitstream`,
|
||||
`thiserror` and (optional, feature `threads`) `rayon`, and builds for `wasm32-unknown-unknown`.
|
||||
|
||||
All constant tables (default CDFs, scan orders, quantiser lookups, filter taps, …) and the
|
||||
named constants are extracted from the specification's Markdown source by
|
||||
[`tools/extract_tables.py`](tools/extract_tables.py) into `src/spec_tables.rs` (every table's
|
||||
shape is checked against its declared dimensions; one missing comma in the spec's
|
||||
`Split_Tx_Size` initialiser is reported and repaired). No code was taken from libaom, dav1d,
|
||||
SVT-AV1 or any other implementation; ffmpeg (libsvtav1 to encode fixtures, libdav1d to decode the
|
||||
reference) is used only as an external test oracle.
|
||||
|
||||
## Conformance status
|
||||
|
||||
| Stage | Spec | Status |
|
||||
|---|---|---|
|
||||
| OBU parsing (low-overhead format), temporal delimiters, operating-point dropping | 5.3, 7.5 | done |
|
||||
| Sequence header, color config, timing / decoder model info | 5.5 | done |
|
||||
| Uncompressed frame header (all fields, frame size / superres / render size, tile info, quantiser, segmentation, delta q/lf, loop filter, CDEF, LR, tx mode, skip mode, global motion, film grain params) | 5.9 | done |
|
||||
| Reference frame state: set_frame_refs, setup_past_independence, load_previous, reference update, show_existing_frame | 7.8, 7.20, 7.21 | done |
|
||||
| Symbol decoder, CDF adaptation, init / load / save / frame-end CDF update | 8.2, 8.3 | done |
|
||||
| Tiles and tile groups (uniform and explicit spacing, tile size bytes) | 5.11 | done (decoded sequentially) |
|
||||
| Intra frame mode info: partitions, skip, segment id, CDEF index, delta q / lf, y / uv modes, angle deltas, CfL alphas, palette (colors, cache, color index map), filter intra, tx size (incl. var-tx syntax) | 5.11 | done, bit-exact |
|
||||
| Coefficients (all_zero, eob, base / br levels, Golomb, dc sign, tx type sets, scans) | 5.11.39 | done, bit-exact |
|
||||
| Dequantisation incl. quantiser matrices | 7.12 | done (qmatrix not yet oracle-tested) |
|
||||
| Inverse transforms: DCT 4–64, ADST 4/8/16, flip ADST, identity 4–32, WHT (lossless), rectangular | 7.13 | done, bit-exact |
|
||||
| Intra prediction: DC, V/H + directional with edge filter and upsampling, smooth (3), Paeth, recursive filter intra, CfL, palette | 7.11.2, 7.11.4, 7.11.5 | done, bit-exact |
|
||||
| Intra block copy | 7.11.3 | missing |
|
||||
| Inter prediction: MV prediction, motion field, scaling, filters, warp / global motion, OBMC, compound / masks, inter-intra | 7.9–7.11.3 | missing |
|
||||
| Loop filter | 7.14 | missing |
|
||||
| CDEF | 7.15 | missing |
|
||||
| Super-resolution upscaling | 7.16 | missing |
|
||||
| Loop restoration (Wiener, self-guided) | 7.17 | syntax done, filter missing |
|
||||
| Film grain synthesis | 7.18.3 | missing |
|
||||
| Large-scale tile / tile list OBUs | 7.3 | not planned |
|
||||
|
||||
## Accuracy
|
||||
|
||||
`tests/oracle_intra.rs` encodes all-intra streams with ffmpeg's libsvtav1 (in-loop filters off)
|
||||
and compares every decoded frame with libdav1d: **bit-exact** on all fixtures (8- and 10-bit
|
||||
4:2:0; 128×128 to 1280×720 including odd sizes; testsrc2, mandelbrot and noise; presets 2–8).
|
||||
|
||||
## API
|
||||
|
||||
```rust
|
||||
let mut dec = filmcraft_av1::Decoder::new();
|
||||
for sample in samples { // one temporal unit (MP4 / Matroska sample) each
|
||||
for pic in dec.decode(&sample)? { // shown frames, planar u16
|
||||
// pic.width, pic.height, pic.bit_depth, pic.planes[0..3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
//! MSB-first bit reader for OBU headers and the uncompressed frame header (spec 4.10 descriptors).
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BitReader<'a> {
|
||||
data: &'a [u8],
|
||||
/// Position in bits.
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> BitReader<'a> {
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
BitReader { data, pos: 0 }
|
||||
}
|
||||
|
||||
/// `get_position()`: bits consumed.
|
||||
pub fn position(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
|
||||
pub fn byte_pos(&self) -> usize {
|
||||
self.pos.div_ceil(8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit(&mut self) -> Result<u32> {
|
||||
let byte = *self.data.get(self.pos >> 3).ok_or(Error::Truncated)?;
|
||||
let b = (byte >> (7 - (self.pos & 7))) & 1;
|
||||
self.pos += 1;
|
||||
Ok(b as u32)
|
||||
}
|
||||
|
||||
/// `f(n)`, n ≤ 32.
|
||||
pub fn f(&mut self, n: u32) -> Result<u32> {
|
||||
let mut x: u64 = 0;
|
||||
for _ in 0..n {
|
||||
x = (x << 1) | self.bit()? as u64;
|
||||
}
|
||||
Ok(x as u32)
|
||||
}
|
||||
|
||||
pub fn flag(&mut self) -> Result<bool> {
|
||||
Ok(self.bit()? == 1)
|
||||
}
|
||||
|
||||
/// `uvlc()`
|
||||
pub fn uvlc(&mut self) -> Result<u32> {
|
||||
let mut leading = 0u32;
|
||||
while self.bit()? == 0 {
|
||||
leading += 1;
|
||||
if leading > 40 {
|
||||
return Err(Error::Invalid("uvlc"));
|
||||
}
|
||||
}
|
||||
if leading >= 32 {
|
||||
return Ok(u32::MAX);
|
||||
}
|
||||
let v = self.f(leading)?;
|
||||
Ok(v + ((1u64 << leading) - 1) as u32)
|
||||
}
|
||||
|
||||
/// `le(n)`
|
||||
pub fn le(&mut self, n: u32) -> Result<u32> {
|
||||
let mut t = 0u32;
|
||||
for i in 0..n {
|
||||
t |= self.f(8)? << (8 * i);
|
||||
}
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
/// `leb128()`
|
||||
pub fn leb128(&mut self) -> Result<u64> {
|
||||
let mut v = 0u64;
|
||||
for i in 0..8 {
|
||||
let b = self.f(8)? as u64;
|
||||
v |= (b & 0x7f) << (7 * i);
|
||||
if b & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// `su(n)`
|
||||
pub fn su(&mut self, n: u32) -> Result<i32> {
|
||||
let v = self.f(n)? as i64;
|
||||
let sign = 1i64 << (n - 1);
|
||||
Ok(if v & sign != 0 { (v - 2 * sign) as i32 } else { v as i32 })
|
||||
}
|
||||
|
||||
/// `ns(n)`
|
||||
pub fn ns(&mut self, n: u32) -> Result<u32> {
|
||||
if n <= 1 {
|
||||
return Ok(0);
|
||||
}
|
||||
let w = 32 - n.leading_zeros(); // FloorLog2(n) + 1
|
||||
let m = (1u32 << w) - n;
|
||||
let v = self.f(w - 1)?;
|
||||
if v < m {
|
||||
return Ok(v);
|
||||
}
|
||||
let extra = self.bit()?;
|
||||
Ok((v << 1) - m + extra)
|
||||
}
|
||||
|
||||
/// `byte_alignment()`
|
||||
pub fn byte_align(&mut self) {
|
||||
self.pos = self.pos.div_ceil(8) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a leb128 from a byte slice; returns (value, bytes used).
|
||||
pub(crate) fn leb128(data: &[u8]) -> Result<(u64, usize)> {
|
||||
let mut v = 0u64;
|
||||
for i in 0..8 {
|
||||
let b = *data.get(i).ok_or(Error::Truncated)? as u64;
|
||||
v |= (b & 0x7f) << (7 * i);
|
||||
if b & 0x80 == 0 {
|
||||
return Ok((v, i + 1));
|
||||
}
|
||||
}
|
||||
Ok((v, 8))
|
||||
}
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
//! The adaptive CDF arrays (spec 6.8.2 init_non_coeff_cdfs / init_coeff_cdfs, load_cdfs).
|
||||
|
||||
use crate::spec_tables::*;
|
||||
|
||||
/// Every CDF array listed by init_non_coeff_cdfs and init_coeff_cdfs. Each innermost array holds
|
||||
/// N cumulative values (the last is 32768) plus the adaptation counter.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Cdfs {
|
||||
pub y_mode: [[u16; 14]; 4],
|
||||
pub uv_mode_cfl_not_allowed: [[u16; 14]; 13],
|
||||
pub uv_mode_cfl_allowed: [[u16; 15]; 13],
|
||||
pub angle_delta: [[u16; 8]; 8],
|
||||
pub intrabc: [u16; 3],
|
||||
pub partition_w8: [[u16; 5]; 4],
|
||||
pub partition_w16: [[u16; 11]; 4],
|
||||
pub partition_w32: [[u16; 11]; 4],
|
||||
pub partition_w64: [[u16; 11]; 4],
|
||||
pub partition_w128: [[u16; 9]; 4],
|
||||
pub segment_id: [[u16; 9]; 3],
|
||||
pub segment_id_predicted: [[u16; 3]; 3],
|
||||
pub tx_8x8: [[u16; 3]; 3],
|
||||
pub tx_16x16: [[u16; 4]; 3],
|
||||
pub tx_32x32: [[u16; 4]; 3],
|
||||
pub tx_64x64: [[u16; 4]; 3],
|
||||
pub txfm_split: [[u16; 3]; 21],
|
||||
pub filter_intra_mode: [u16; 6],
|
||||
pub filter_intra: [[u16; 3]; 22],
|
||||
pub interp_filter: [[u16; 4]; 16],
|
||||
pub motion_mode: [[u16; 4]; 22],
|
||||
pub new_mv: [[u16; 3]; 6],
|
||||
pub zero_mv: [[u16; 3]; 2],
|
||||
pub ref_mv: [[u16; 3]; 6],
|
||||
pub compound_mode: [[u16; 9]; 8],
|
||||
pub drl_mode: [[u16; 3]; 3],
|
||||
pub is_inter: [[u16; 3]; 4],
|
||||
pub comp_mode: [[u16; 3]; 5],
|
||||
pub skip_mode: [[u16; 3]; 3],
|
||||
pub skip: [[u16; 3]; 3],
|
||||
pub comp_ref: [[[u16; 3]; 3]; 3],
|
||||
pub comp_bwd_ref: [[[u16; 3]; 2]; 3],
|
||||
pub single_ref: [[[u16; 3]; 6]; 3],
|
||||
pub mv_joint: [[u16; 5]; MV_CONTEXTS],
|
||||
pub mv_class: [[[u16; 12]; 2]; MV_CONTEXTS],
|
||||
pub mv_class0_bit: [[[u16; 3]; 2]; MV_CONTEXTS],
|
||||
pub mv_fr: [[[u16; 5]; 2]; MV_CONTEXTS],
|
||||
pub mv_class0_fr: [[[[u16; 5]; 2]; 2]; MV_CONTEXTS],
|
||||
pub mv_class0_hp: [[[u16; 3]; 2]; MV_CONTEXTS],
|
||||
pub mv_sign: [[[u16; 3]; 2]; MV_CONTEXTS],
|
||||
pub mv_bit: [[[[u16; 3]; 10]; 2]; MV_CONTEXTS],
|
||||
pub mv_hp: [[[u16; 3]; 2]; MV_CONTEXTS],
|
||||
pub palette_y_mode: [[[u16; 3]; 3]; 7],
|
||||
pub palette_uv_mode: [[u16; 3]; 2],
|
||||
pub palette_y_size: [[u16; 8]; 7],
|
||||
pub palette_uv_size: [[u16; 8]; 7],
|
||||
pub palette_2_y_color: [[u16; 3]; 5],
|
||||
pub palette_2_uv_color: [[u16; 3]; 5],
|
||||
pub palette_3_y_color: [[u16; 4]; 5],
|
||||
pub palette_3_uv_color: [[u16; 4]; 5],
|
||||
pub palette_4_y_color: [[u16; 5]; 5],
|
||||
pub palette_4_uv_color: [[u16; 5]; 5],
|
||||
pub palette_5_y_color: [[u16; 6]; 5],
|
||||
pub palette_5_uv_color: [[u16; 6]; 5],
|
||||
pub palette_6_y_color: [[u16; 7]; 5],
|
||||
pub palette_6_uv_color: [[u16; 7]; 5],
|
||||
pub palette_7_y_color: [[u16; 8]; 5],
|
||||
pub palette_7_uv_color: [[u16; 8]; 5],
|
||||
pub palette_8_y_color: [[u16; 9]; 5],
|
||||
pub palette_8_uv_color: [[u16; 9]; 5],
|
||||
pub delta_q: [u16; 5],
|
||||
pub delta_lf: [u16; 5],
|
||||
pub delta_lf_multi: [[u16; 5]; FRAME_LF_COUNT],
|
||||
pub intra_tx_type_set1: [[[u16; 8]; 13]; 2],
|
||||
pub intra_tx_type_set2: [[[u16; 6]; 13]; 3],
|
||||
pub inter_tx_type_set1: [[u16; 17]; 2],
|
||||
pub inter_tx_type_set2: [u16; 13],
|
||||
pub inter_tx_type_set3: [[u16; 3]; 4],
|
||||
pub use_obmc: [[u16; 3]; 22],
|
||||
pub inter_intra: [[u16; 3]; 3],
|
||||
pub comp_ref_type: [[u16; 3]; 5],
|
||||
pub cfl_sign: [u16; 9],
|
||||
pub uni_comp_ref: [[[u16; 3]; 3]; 3],
|
||||
pub wedge_inter_intra: [[u16; 3]; 22],
|
||||
pub comp_group_idx: [[u16; 3]; 6],
|
||||
pub compound_idx: [[u16; 3]; 6],
|
||||
pub compound_type: [[u16; 3]; 22],
|
||||
pub inter_intra_mode: [[u16; 5]; 3],
|
||||
pub wedge_index: [[u16; 17]; 22],
|
||||
pub cfl_alpha: [[u16; 17]; 6],
|
||||
pub use_wiener: [u16; 3],
|
||||
pub use_sgrproj: [u16; 3],
|
||||
pub restoration_type: [u16; 4],
|
||||
// coefficient CDFs
|
||||
pub txb_skip: [[[u16; 3]; 13]; 5],
|
||||
pub eob_pt_16: [[[u16; 6]; 2]; 2],
|
||||
pub eob_pt_32: [[[u16; 7]; 2]; 2],
|
||||
pub eob_pt_64: [[[u16; 8]; 2]; 2],
|
||||
pub eob_pt_128: [[[u16; 9]; 2]; 2],
|
||||
pub eob_pt_256: [[[u16; 10]; 2]; 2],
|
||||
pub eob_pt_512: [[u16; 11]; 2],
|
||||
pub eob_pt_1024: [[u16; 12]; 2],
|
||||
pub eob_extra: [[[[u16; 3]; 9]; 2]; 5],
|
||||
pub dc_sign: [[[u16; 3]; 3]; 2],
|
||||
pub coeff_base_eob: [[[[u16; 4]; 4]; 2]; 5],
|
||||
pub coeff_base: [[[[u16; 5]; 42]; 2]; 5],
|
||||
pub coeff_br: [[[[u16; 5]; 21]; 2]; 5],
|
||||
}
|
||||
|
||||
impl Cdfs {
|
||||
/// init_non_coeff_cdfs( ) followed by init_coeff_cdfs( ) for `base_q_idx`.
|
||||
pub fn new(base_q_idx: u32) -> Box<Cdfs> {
|
||||
let mut c = Box::new(Cdfs {
|
||||
y_mode: DEFAULT_Y_MODE_CDF,
|
||||
uv_mode_cfl_not_allowed: DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF,
|
||||
uv_mode_cfl_allowed: DEFAULT_UV_MODE_CFL_ALLOWED_CDF,
|
||||
angle_delta: DEFAULT_ANGLE_DELTA_CDF,
|
||||
intrabc: DEFAULT_INTRABC_CDF,
|
||||
partition_w8: DEFAULT_PARTITION_W8_CDF,
|
||||
partition_w16: DEFAULT_PARTITION_W16_CDF,
|
||||
partition_w32: DEFAULT_PARTITION_W32_CDF,
|
||||
partition_w64: DEFAULT_PARTITION_W64_CDF,
|
||||
partition_w128: DEFAULT_PARTITION_W128_CDF,
|
||||
segment_id: DEFAULT_SEGMENT_ID_CDF,
|
||||
segment_id_predicted: DEFAULT_SEGMENT_ID_PREDICTED_CDF,
|
||||
tx_8x8: DEFAULT_TX_8X8_CDF,
|
||||
tx_16x16: DEFAULT_TX_16X16_CDF,
|
||||
tx_32x32: DEFAULT_TX_32X32_CDF,
|
||||
tx_64x64: DEFAULT_TX_64X64_CDF,
|
||||
txfm_split: DEFAULT_TXFM_SPLIT_CDF,
|
||||
filter_intra_mode: DEFAULT_FILTER_INTRA_MODE_CDF,
|
||||
filter_intra: DEFAULT_FILTER_INTRA_CDF,
|
||||
interp_filter: DEFAULT_INTERP_FILTER_CDF,
|
||||
motion_mode: DEFAULT_MOTION_MODE_CDF,
|
||||
new_mv: DEFAULT_NEW_MV_CDF,
|
||||
zero_mv: DEFAULT_ZERO_MV_CDF,
|
||||
ref_mv: DEFAULT_REF_MV_CDF,
|
||||
compound_mode: DEFAULT_COMPOUND_MODE_CDF,
|
||||
drl_mode: DEFAULT_DRL_MODE_CDF,
|
||||
is_inter: DEFAULT_IS_INTER_CDF,
|
||||
comp_mode: DEFAULT_COMP_MODE_CDF,
|
||||
skip_mode: DEFAULT_SKIP_MODE_CDF,
|
||||
skip: DEFAULT_SKIP_CDF,
|
||||
comp_ref: DEFAULT_COMP_REF_CDF,
|
||||
comp_bwd_ref: DEFAULT_COMP_BWD_REF_CDF,
|
||||
single_ref: DEFAULT_SINGLE_REF_CDF,
|
||||
mv_joint: [DEFAULT_MV_JOINT_CDF; MV_CONTEXTS],
|
||||
mv_class: [DEFAULT_MV_CLASS_CDF; MV_CONTEXTS],
|
||||
mv_class0_bit: [[DEFAULT_MV_CLASS0_BIT_CDF; 2]; MV_CONTEXTS],
|
||||
mv_fr: [DEFAULT_MV_FR_CDF; MV_CONTEXTS],
|
||||
mv_class0_fr: [DEFAULT_MV_CLASS0_FR_CDF; MV_CONTEXTS],
|
||||
mv_class0_hp: [[DEFAULT_MV_CLASS0_HP_CDF; 2]; MV_CONTEXTS],
|
||||
mv_sign: [[DEFAULT_MV_SIGN_CDF; 2]; MV_CONTEXTS],
|
||||
mv_bit: [[DEFAULT_MV_BIT_CDF; 2]; MV_CONTEXTS],
|
||||
mv_hp: [[DEFAULT_MV_HP_CDF; 2]; MV_CONTEXTS],
|
||||
palette_y_mode: DEFAULT_PALETTE_Y_MODE_CDF,
|
||||
palette_uv_mode: DEFAULT_PALETTE_UV_MODE_CDF,
|
||||
palette_y_size: DEFAULT_PALETTE_Y_SIZE_CDF,
|
||||
palette_uv_size: DEFAULT_PALETTE_UV_SIZE_CDF,
|
||||
palette_2_y_color: DEFAULT_PALETTE_SIZE_2_Y_COLOR_CDF,
|
||||
palette_2_uv_color: DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF,
|
||||
palette_3_y_color: DEFAULT_PALETTE_SIZE_3_Y_COLOR_CDF,
|
||||
palette_3_uv_color: DEFAULT_PALETTE_SIZE_3_UV_COLOR_CDF,
|
||||
palette_4_y_color: DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF,
|
||||
palette_4_uv_color: DEFAULT_PALETTE_SIZE_4_UV_COLOR_CDF,
|
||||
palette_5_y_color: DEFAULT_PALETTE_SIZE_5_Y_COLOR_CDF,
|
||||
palette_5_uv_color: DEFAULT_PALETTE_SIZE_5_UV_COLOR_CDF,
|
||||
palette_6_y_color: DEFAULT_PALETTE_SIZE_6_Y_COLOR_CDF,
|
||||
palette_6_uv_color: DEFAULT_PALETTE_SIZE_6_UV_COLOR_CDF,
|
||||
palette_7_y_color: DEFAULT_PALETTE_SIZE_7_Y_COLOR_CDF,
|
||||
palette_7_uv_color: DEFAULT_PALETTE_SIZE_7_UV_COLOR_CDF,
|
||||
palette_8_y_color: DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF,
|
||||
palette_8_uv_color: DEFAULT_PALETTE_SIZE_8_UV_COLOR_CDF,
|
||||
delta_q: DEFAULT_DELTA_Q_CDF,
|
||||
delta_lf: DEFAULT_DELTA_LF_CDF,
|
||||
delta_lf_multi: [DEFAULT_DELTA_LF_CDF; FRAME_LF_COUNT],
|
||||
intra_tx_type_set1: DEFAULT_INTRA_TX_TYPE_SET1_CDF,
|
||||
intra_tx_type_set2: DEFAULT_INTRA_TX_TYPE_SET2_CDF,
|
||||
inter_tx_type_set1: DEFAULT_INTER_TX_TYPE_SET1_CDF,
|
||||
inter_tx_type_set2: DEFAULT_INTER_TX_TYPE_SET2_CDF,
|
||||
inter_tx_type_set3: DEFAULT_INTER_TX_TYPE_SET3_CDF,
|
||||
use_obmc: DEFAULT_USE_OBMC_CDF,
|
||||
inter_intra: DEFAULT_INTER_INTRA_CDF,
|
||||
comp_ref_type: DEFAULT_COMP_REF_TYPE_CDF,
|
||||
cfl_sign: DEFAULT_CFL_SIGN_CDF,
|
||||
uni_comp_ref: DEFAULT_UNI_COMP_REF_CDF,
|
||||
wedge_inter_intra: DEFAULT_WEDGE_INTER_INTRA_CDF,
|
||||
comp_group_idx: DEFAULT_COMP_GROUP_IDX_CDF,
|
||||
compound_idx: DEFAULT_COMPOUND_IDX_CDF,
|
||||
compound_type: DEFAULT_COMPOUND_TYPE_CDF,
|
||||
inter_intra_mode: DEFAULT_INTER_INTRA_MODE_CDF,
|
||||
wedge_index: DEFAULT_WEDGE_INDEX_CDF,
|
||||
cfl_alpha: DEFAULT_CFL_ALPHA_CDF,
|
||||
use_wiener: DEFAULT_USE_WIENER_CDF,
|
||||
use_sgrproj: DEFAULT_USE_SGRPROJ_CDF,
|
||||
restoration_type: DEFAULT_RESTORATION_TYPE_CDF,
|
||||
txb_skip: DEFAULT_TXB_SKIP_CDF[0],
|
||||
eob_pt_16: DEFAULT_EOB_PT_16_CDF[0],
|
||||
eob_pt_32: DEFAULT_EOB_PT_32_CDF[0],
|
||||
eob_pt_64: DEFAULT_EOB_PT_64_CDF[0],
|
||||
eob_pt_128: DEFAULT_EOB_PT_128_CDF[0],
|
||||
eob_pt_256: DEFAULT_EOB_PT_256_CDF[0],
|
||||
eob_pt_512: DEFAULT_EOB_PT_512_CDF[0],
|
||||
eob_pt_1024: DEFAULT_EOB_PT_1024_CDF[0],
|
||||
eob_extra: DEFAULT_EOB_EXTRA_CDF[0],
|
||||
dc_sign: DEFAULT_DC_SIGN_CDF[0],
|
||||
coeff_base_eob: DEFAULT_COEFF_BASE_EOB_CDF[0],
|
||||
coeff_base: DEFAULT_COEFF_BASE_CDF[0],
|
||||
coeff_br: DEFAULT_COEFF_BR_CDF[0],
|
||||
});
|
||||
c.init_coeff(base_q_idx);
|
||||
c
|
||||
}
|
||||
|
||||
/// init_coeff_cdfs( )
|
||||
pub fn init_coeff(&mut self, base_q_idx: u32) {
|
||||
let idx = match base_q_idx {
|
||||
0..=20 => 0,
|
||||
21..=60 => 1,
|
||||
61..=120 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
self.txb_skip = DEFAULT_TXB_SKIP_CDF[idx];
|
||||
self.eob_pt_16 = DEFAULT_EOB_PT_16_CDF[idx];
|
||||
self.eob_pt_32 = DEFAULT_EOB_PT_32_CDF[idx];
|
||||
self.eob_pt_64 = DEFAULT_EOB_PT_64_CDF[idx];
|
||||
self.eob_pt_128 = DEFAULT_EOB_PT_128_CDF[idx];
|
||||
self.eob_pt_256 = DEFAULT_EOB_PT_256_CDF[idx];
|
||||
self.eob_pt_512 = DEFAULT_EOB_PT_512_CDF[idx];
|
||||
self.eob_pt_1024 = DEFAULT_EOB_PT_1024_CDF[idx];
|
||||
self.eob_extra = DEFAULT_EOB_EXTRA_CDF[idx];
|
||||
self.dc_sign = DEFAULT_DC_SIGN_CDF[idx];
|
||||
self.coeff_base_eob = DEFAULT_COEFF_BASE_EOB_CDF[idx];
|
||||
self.coeff_base = DEFAULT_COEFF_BASE_CDF[idx];
|
||||
self.coeff_br = DEFAULT_COEFF_BR_CDF[idx];
|
||||
}
|
||||
|
||||
/// The counter reset of load_cdfs( ): the last entry of every CDF array is set to 0.
|
||||
pub fn clear_counts(&mut self) {
|
||||
macro_rules! clr {
|
||||
($($f:ident),*) => { $( self.$f.clear_count(); )* };
|
||||
}
|
||||
clr!(
|
||||
y_mode,
|
||||
uv_mode_cfl_not_allowed,
|
||||
uv_mode_cfl_allowed,
|
||||
angle_delta,
|
||||
intrabc,
|
||||
partition_w8,
|
||||
partition_w16,
|
||||
partition_w32,
|
||||
partition_w64,
|
||||
partition_w128,
|
||||
segment_id,
|
||||
segment_id_predicted,
|
||||
tx_8x8,
|
||||
tx_16x16,
|
||||
tx_32x32,
|
||||
tx_64x64,
|
||||
txfm_split,
|
||||
filter_intra_mode,
|
||||
filter_intra,
|
||||
interp_filter,
|
||||
motion_mode,
|
||||
new_mv,
|
||||
zero_mv,
|
||||
ref_mv,
|
||||
compound_mode,
|
||||
drl_mode,
|
||||
is_inter,
|
||||
comp_mode,
|
||||
skip_mode,
|
||||
skip,
|
||||
comp_ref,
|
||||
comp_bwd_ref,
|
||||
single_ref,
|
||||
mv_joint,
|
||||
mv_class,
|
||||
mv_class0_bit,
|
||||
mv_fr,
|
||||
mv_class0_fr,
|
||||
mv_class0_hp,
|
||||
mv_sign,
|
||||
mv_bit,
|
||||
mv_hp,
|
||||
palette_y_mode,
|
||||
palette_uv_mode,
|
||||
palette_y_size,
|
||||
palette_uv_size,
|
||||
palette_2_y_color,
|
||||
palette_2_uv_color,
|
||||
palette_3_y_color,
|
||||
palette_3_uv_color,
|
||||
palette_4_y_color,
|
||||
palette_4_uv_color,
|
||||
palette_5_y_color,
|
||||
palette_5_uv_color,
|
||||
palette_6_y_color,
|
||||
palette_6_uv_color,
|
||||
palette_7_y_color,
|
||||
palette_7_uv_color,
|
||||
palette_8_y_color,
|
||||
palette_8_uv_color,
|
||||
delta_q,
|
||||
delta_lf,
|
||||
delta_lf_multi,
|
||||
intra_tx_type_set1,
|
||||
intra_tx_type_set2,
|
||||
inter_tx_type_set1,
|
||||
inter_tx_type_set2,
|
||||
inter_tx_type_set3,
|
||||
use_obmc,
|
||||
inter_intra,
|
||||
comp_ref_type,
|
||||
cfl_sign,
|
||||
uni_comp_ref,
|
||||
wedge_inter_intra,
|
||||
comp_group_idx,
|
||||
compound_idx,
|
||||
compound_type,
|
||||
inter_intra_mode,
|
||||
wedge_index,
|
||||
cfl_alpha,
|
||||
use_wiener,
|
||||
use_sgrproj,
|
||||
restoration_type,
|
||||
txb_skip,
|
||||
eob_pt_16,
|
||||
eob_pt_32,
|
||||
eob_pt_64,
|
||||
eob_pt_128,
|
||||
eob_pt_256,
|
||||
eob_pt_512,
|
||||
eob_pt_1024,
|
||||
eob_extra,
|
||||
dc_sign,
|
||||
coeff_base_eob,
|
||||
coeff_base,
|
||||
coeff_br
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Zeroes the adaptation counter (last element) of every innermost CDF array.
|
||||
trait ClearCount {
|
||||
fn clear_count(&mut self);
|
||||
}
|
||||
|
||||
impl<const N: usize> ClearCount for [u16; N] {
|
||||
fn clear_count(&mut self) {
|
||||
self[N - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ClearCount, const M: usize> ClearCount for [T; M] {
|
||||
fn clear_count(&mut self) {
|
||||
for e in self.iter_mut() {
|
||||
e.clear_count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
//! Top-level decoder: OBU parsing (5.3), frame / tile group handling, reference frame update
|
||||
//! (7.20), show-existing-frame and output (7.18).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::bits::{BitReader, leb128};
|
||||
use crate::cdf::Cdfs;
|
||||
use crate::frame::FrameBuf;
|
||||
use crate::header::{FrameHeader, HeaderState, LoopFilterDeltas, RefInfo, SegmentationFeatures, SequenceHeader, default_gm_params};
|
||||
use crate::spec_tables::*;
|
||||
use crate::state::FrameState;
|
||||
use crate::tile::TileDecoder;
|
||||
use crate::{Error, Picture, Result};
|
||||
|
||||
const OBU_SEQUENCE_HEADER_T: u32 = 1;
|
||||
const OBU_TEMPORAL_DELIMITER_T: u32 = 2;
|
||||
const OBU_FRAME_HEADER_T: u32 = 3;
|
||||
const OBU_TILE_GROUP_T: u32 = 4;
|
||||
const OBU_METADATA_T: u32 = 5;
|
||||
const OBU_FRAME_T: u32 = 6;
|
||||
const OBU_REDUNDANT_FRAME_HEADER_T: u32 = 7;
|
||||
const OBU_TILE_LIST_T: u32 = 8;
|
||||
|
||||
/// A stored reference frame (FrameStore plus the saved per-frame state).
|
||||
pub(crate) struct RefFrame {
|
||||
pub buf: FrameBuf,
|
||||
pub cdfs: Box<Cdfs>,
|
||||
pub bit_depth: u8,
|
||||
pub subsampling_x: u8,
|
||||
pub subsampling_y: u8,
|
||||
pub segment_ids: Vec<u8>,
|
||||
pub mi_cols: usize,
|
||||
pub mi_rows: usize,
|
||||
pub film_grain_present: bool,
|
||||
pub color: (u8, u8, u8, bool),
|
||||
}
|
||||
|
||||
/// An AV1 decoder. Feed it temporal units (one MP4 / Matroska sample each, or any run of
|
||||
/// OBUs in the low-overhead format) and collect the shown frames.
|
||||
pub struct Decoder {
|
||||
seq: Option<SequenceHeader>,
|
||||
refs: [RefInfo; NUM_REF_FRAMES],
|
||||
ref_frames: [Option<Arc<RefFrame>>; NUM_REF_FRAMES],
|
||||
lf_deltas: LoopFilterDeltas,
|
||||
seg_features: SegmentationFeatures,
|
||||
prev_gm_params: [[i32; 6]; 8],
|
||||
current_frame_id: u32,
|
||||
/// Frame being decoded (between its header and its last tile group).
|
||||
cur: Option<Box<CurFrame>>,
|
||||
seen_frame_header: bool,
|
||||
/// Apply film grain synthesis to output frames (default true).
|
||||
pub apply_film_grain: bool,
|
||||
}
|
||||
|
||||
struct CurFrame {
|
||||
fs: FrameState,
|
||||
cdfs: Box<Cdfs>,
|
||||
saved_cdfs: Option<Box<Cdfs>>,
|
||||
}
|
||||
|
||||
impl Default for Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
pub fn new() -> Decoder {
|
||||
Decoder {
|
||||
seq: None,
|
||||
refs: Default::default(),
|
||||
ref_frames: Default::default(),
|
||||
lf_deltas: LoopFilterDeltas::defaults(),
|
||||
seg_features: SegmentationFeatures::default(),
|
||||
prev_gm_params: default_gm_params(),
|
||||
current_frame_id: 0,
|
||||
cur: None,
|
||||
seen_frame_header: false,
|
||||
apply_film_grain: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// The active sequence header, once one has been decoded.
|
||||
pub fn sequence_header(&self) -> Option<&SequenceHeader> {
|
||||
self.seq.as_ref()
|
||||
}
|
||||
|
||||
/// Decode a chunk of OBUs; returns the frames shown by it.
|
||||
pub fn decode(&mut self, data: &[u8]) -> Result<Vec<Picture>> {
|
||||
let mut out = Vec::new();
|
||||
let mut pos = 0;
|
||||
while pos < data.len() {
|
||||
let h = data[pos];
|
||||
if h & 0x80 != 0 {
|
||||
return Err(Error::Invalid("obu_forbidden_bit"));
|
||||
}
|
||||
let obu_type = ((h >> 3) & 0xf) as u32;
|
||||
let ext = h & 4 != 0;
|
||||
let has_size = h & 2 != 0;
|
||||
let mut p = pos + 1;
|
||||
let (mut temporal_id, mut spatial_id) = (0, 0);
|
||||
if ext {
|
||||
let e = *data.get(p).ok_or(Error::Truncated)?;
|
||||
temporal_id = (e >> 5) as u32;
|
||||
spatial_id = ((e >> 3) & 3) as u32;
|
||||
p += 1;
|
||||
}
|
||||
let size = if has_size {
|
||||
let (v, n) = leb128(&data[p..])?;
|
||||
p += n;
|
||||
v as usize
|
||||
} else {
|
||||
data.len() - p
|
||||
};
|
||||
let end = p.checked_add(size).filter(|&e| e <= data.len()).ok_or(Error::Truncated)?;
|
||||
let payload = &data[p..end];
|
||||
pos = end;
|
||||
if obu_type != OBU_SEQUENCE_HEADER_T
|
||||
&& obu_type != OBU_TEMPORAL_DELIMITER_T
|
||||
&& ext
|
||||
&& let Some(seq) = &self.seq
|
||||
&& seq.op_idc != 0
|
||||
{
|
||||
let idc = seq.op_idc;
|
||||
let in_t = (idc >> temporal_id) & 1;
|
||||
let in_s = (idc >> (spatial_id + 8)) & 1;
|
||||
if in_t == 0 || in_s == 0 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
match obu_type {
|
||||
OBU_SEQUENCE_HEADER_T => {
|
||||
let s = SequenceHeader::parse(payload)?;
|
||||
self.seq = Some(s);
|
||||
}
|
||||
OBU_TEMPORAL_DELIMITER_T => self.seen_frame_header = false,
|
||||
OBU_FRAME_HEADER_T | OBU_REDUNDANT_FRAME_HEADER_T | OBU_FRAME_T => {
|
||||
if self.seen_frame_header {
|
||||
// frame_header_copy(): identical to the active header
|
||||
if obu_type != OBU_FRAME_T {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let mut r = BitReader::new(payload);
|
||||
let shown = self.frame_header(&mut r, temporal_id, spatial_id)?;
|
||||
if let Some(pic) = shown {
|
||||
out.push(pic);
|
||||
continue;
|
||||
}
|
||||
if obu_type == OBU_FRAME_T {
|
||||
r.byte_align();
|
||||
let rest = &payload[r.byte_pos()..];
|
||||
if let Some(pic) = self.tile_group(rest)? {
|
||||
out.push(pic);
|
||||
}
|
||||
}
|
||||
}
|
||||
OBU_TILE_GROUP_T => {
|
||||
if let Some(pic) = self.tile_group(payload)? {
|
||||
out.push(pic);
|
||||
}
|
||||
}
|
||||
OBU_METADATA_T | OBU_TILE_LIST_T => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// frame_header_obu( ): returns a picture for show_existing_frame.
|
||||
fn frame_header(&mut self, r: &mut BitReader, temporal_id: u32, spatial_id: u32) -> Result<Option<Picture>> {
|
||||
let seq = self.seq.clone().ok_or(Error::Invalid("frame before sequence header"))?;
|
||||
self.seen_frame_header = true;
|
||||
let mut st = HeaderState {
|
||||
seq: &seq,
|
||||
refs: &mut self.refs,
|
||||
lf_deltas: self.lf_deltas,
|
||||
seg_features: self.seg_features,
|
||||
prev_gm_params: self.prev_gm_params,
|
||||
current_frame_id: self.current_frame_id,
|
||||
};
|
||||
let fh = FrameHeader::parse(r, &mut st, temporal_id, spatial_id)?;
|
||||
self.lf_deltas = st.lf_deltas;
|
||||
self.seg_features = st.seg_features;
|
||||
self.prev_gm_params = st.prev_gm_params;
|
||||
self.current_frame_id = st.current_frame_id;
|
||||
if fh.show_existing_frame {
|
||||
self.seen_frame_header = false;
|
||||
let idx = fh.frame_to_show_map_idx;
|
||||
let rf = self.ref_frames[idx].clone().ok_or(Error::Invalid("show_existing_frame of an empty slot"))?;
|
||||
let info = self.refs[idx].clone();
|
||||
let pic = self.output_picture(&seq, &rf, info.upscaled_width as usize, info.frame_height as usize, &fh.film_grain);
|
||||
if fh.frame_type == KEY_FRAME as u8 {
|
||||
// reference frame loading process (7.21) then refresh every slot (7.20)
|
||||
self.lf_deltas = info.lf_deltas;
|
||||
self.seg_features = info.seg_features;
|
||||
for i in 0..NUM_REF_FRAMES {
|
||||
self.refs[i] = info.clone();
|
||||
self.ref_frames[i] = Some(rf.clone());
|
||||
}
|
||||
}
|
||||
return Ok(Some(pic));
|
||||
}
|
||||
if fh.frame_width > seq.max_frame_width || fh.frame_height > seq.max_frame_height {
|
||||
return Err(Error::Invalid("frame larger than the sequence maximum"));
|
||||
}
|
||||
let fs = FrameState::new(&seq, &fh);
|
||||
let cdfs = if fh.primary_ref_frame == PRIMARY_REF_NONE {
|
||||
Cdfs::new(fh.quant.base_q_idx)
|
||||
} else {
|
||||
let slot = fh.ref_frame_idx[fh.primary_ref_frame];
|
||||
let rf = self.ref_frames[slot].as_ref().ok_or(Error::Invalid("primary reference frame missing"))?;
|
||||
let mut c = rf.cdfs.clone();
|
||||
c.clear_counts();
|
||||
c
|
||||
};
|
||||
self.cur = Some(Box::new(CurFrame { fs, cdfs, saved_cdfs: None }));
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// tile_group_obu( sz ): returns the output picture when the frame completes and is shown.
|
||||
fn tile_group(&mut self, data: &[u8]) -> Result<Option<Picture>> {
|
||||
let mut cur = self.cur.take().ok_or(Error::Invalid("tile group without a frame header"))?;
|
||||
let (cols, rows, cols_log2, rows_log2, tsb, ctx_tile) = {
|
||||
let t = &cur.fs.fh.tile_info;
|
||||
(t.cols, t.rows, t.cols_log2, t.rows_log2, t.tile_size_bytes, t.context_update_tile_id as usize)
|
||||
};
|
||||
let num_tiles = cols * rows;
|
||||
let mut r = BitReader::new(data);
|
||||
let mut tg_start = 0;
|
||||
let mut tg_end = num_tiles - 1;
|
||||
if num_tiles > 1 && r.flag()? {
|
||||
let bits = cols_log2 + rows_log2;
|
||||
tg_start = r.f(bits)? as usize;
|
||||
tg_end = r.f(bits)? as usize;
|
||||
}
|
||||
r.byte_align();
|
||||
if tg_end >= num_tiles || tg_start > tg_end {
|
||||
return Err(Error::Invalid("tile group range"));
|
||||
}
|
||||
let mut pos = r.byte_pos();
|
||||
for tile_num in tg_start..=tg_end {
|
||||
let tile_row = tile_num / cols;
|
||||
let tile_col = tile_num % cols;
|
||||
let size = if tile_num == tg_end {
|
||||
data.len() - pos
|
||||
} else {
|
||||
let mut v = 0usize;
|
||||
for i in 0..tsb as usize {
|
||||
v |= (*data.get(pos + i).ok_or(Error::Truncated)? as usize) << (8 * i);
|
||||
}
|
||||
pos += tsb as usize;
|
||||
v + 1
|
||||
};
|
||||
let end = pos.checked_add(size).filter(|&e| e <= data.len()).ok_or(Error::Truncated)?;
|
||||
let tile_data = &data[pos..end];
|
||||
pos = end;
|
||||
let cdfs = cur.cdfs.clone();
|
||||
cur.fs.intra_frame_y_mode_cdf = DEFAULT_INTRA_FRAME_Y_MODE_CDF;
|
||||
let mut td = TileDecoder::new(&mut cur.fs, tile_data, cdfs, tile_row, tile_col);
|
||||
td.decode_tile()?;
|
||||
if !td.fs.fh.disable_frame_end_update_cdf && tile_num == ctx_tile {
|
||||
cur.saved_cdfs = Some(td.cdf);
|
||||
}
|
||||
}
|
||||
if tg_end != num_tiles - 1 {
|
||||
self.cur = Some(cur);
|
||||
return Ok(None);
|
||||
}
|
||||
let mut cur = *cur;
|
||||
if !cur.fs.fh.disable_frame_end_update_cdf
|
||||
&& let Some(saved) = cur.saved_cdfs.take()
|
||||
{
|
||||
cur.cdfs = saved;
|
||||
}
|
||||
self.seen_frame_header = false;
|
||||
self.decode_frame_wrapup(cur)
|
||||
}
|
||||
|
||||
fn decode_frame_wrapup(&mut self, cur: CurFrame) -> Result<Option<Picture>> {
|
||||
let CurFrame { mut fs, cdfs, .. } = cur;
|
||||
crate::postfilter::apply(&mut fs);
|
||||
let seq = fs.seq.clone();
|
||||
let fh = fs.fh.clone();
|
||||
if fh.seg.enabled && !fh.seg.update_map {
|
||||
// SegmentIds = PrevSegmentIds (load_previous_segment_ids)
|
||||
if fh.primary_ref_frame != PRIMARY_REF_NONE
|
||||
&& let Some(rf) = &self.ref_frames[fh.ref_frame_idx[fh.primary_ref_frame]]
|
||||
&& rf.mi_cols == fs.mi.cols
|
||||
&& rf.mi_rows == fs.mi.rows
|
||||
{
|
||||
fs.mi.segment_id.copy_from_slice(&rf.segment_ids);
|
||||
}
|
||||
}
|
||||
let rf = Arc::new(RefFrame {
|
||||
buf: std::mem::take(&mut fs.cur),
|
||||
cdfs,
|
||||
bit_depth: seq.color.bit_depth,
|
||||
subsampling_x: seq.color.subsampling_x,
|
||||
subsampling_y: seq.color.subsampling_y,
|
||||
segment_ids: std::mem::take(&mut fs.mi.segment_id),
|
||||
mi_cols: fs.mi.cols,
|
||||
mi_rows: fs.mi.rows,
|
||||
film_grain_present: seq.film_grain_params_present,
|
||||
color: (seq.color.color_primaries, seq.color.transfer_characteristics, seq.color.matrix_coefficients, seq.color.color_range),
|
||||
});
|
||||
// reference frame update process (7.20)
|
||||
for i in 0..NUM_REF_FRAMES {
|
||||
if (fh.refresh_frame_flags >> i) & 1 == 1 {
|
||||
let ri = &mut self.refs[i];
|
||||
ri.valid = true;
|
||||
ri.frame_id = fh.current_frame_id;
|
||||
ri.upscaled_width = fh.upscaled_width;
|
||||
ri.frame_width = fh.frame_width;
|
||||
ri.frame_height = fh.frame_height;
|
||||
ri.render_width = fh.render_width;
|
||||
ri.render_height = fh.render_height;
|
||||
ri.mi_cols = fh.mi_cols;
|
||||
ri.mi_rows = fh.mi_rows;
|
||||
ri.frame_type = fh.frame_type;
|
||||
ri.order_hint = fh.order_hint;
|
||||
ri.saved_order_hints = fh.order_hints;
|
||||
ri.gm_params = fh.gm_params;
|
||||
ri.lf_deltas = fh.lf.deltas;
|
||||
ri.seg_features = fh.seg.features;
|
||||
ri.grain = fh.film_grain.clone();
|
||||
self.ref_frames[i] = Some(rf.clone());
|
||||
}
|
||||
}
|
||||
if fh.show_frame { Ok(Some(self.output_picture(&seq, &rf, fh.upscaled_width as usize, fh.frame_height as usize, &fh.film_grain))) } else { Ok(None) }
|
||||
}
|
||||
|
||||
fn output_picture(&self, seq: &SequenceHeader, rf: &RefFrame, w: usize, h: usize, grain: &crate::header::FilmGrainParams) -> Picture {
|
||||
let ssx = rf.subsampling_x as usize;
|
||||
let ssy = rf.subsampling_y as usize;
|
||||
let num_planes = rf.buf.num_planes;
|
||||
let mut planes: [Vec<u16>; 3] = Default::default();
|
||||
for p in 0..num_planes {
|
||||
let (pw, ph) = if p == 0 { (w, h) } else { ((w + ssx) >> ssx, (h + ssy) >> ssy) };
|
||||
let src = &rf.buf.planes[p];
|
||||
let mut v = Vec::with_capacity(pw * ph);
|
||||
for y in 0..ph {
|
||||
v.extend_from_slice(&src.row(y)[..pw]);
|
||||
}
|
||||
planes[p] = v;
|
||||
}
|
||||
let mut pic = Picture {
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
bit_depth: rf.bit_depth,
|
||||
subsampling_x: rf.subsampling_x,
|
||||
subsampling_y: rf.subsampling_y,
|
||||
mono_chrome: num_planes == 1,
|
||||
planes,
|
||||
color_primaries: rf.color.0,
|
||||
transfer_characteristics: rf.color.1,
|
||||
matrix_coefficients: rf.color.2,
|
||||
full_range: rf.color.3,
|
||||
};
|
||||
if self.apply_film_grain && seq.film_grain_params_present && grain.apply_grain {
|
||||
crate::grain::apply(&mut pic, grain, seq);
|
||||
}
|
||||
pic
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
//! Frame sample buffers and per-4x4 mode info storage.
|
||||
|
||||
/// One plane of samples (u16 at every bit depth).
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Plane {
|
||||
pub data: Vec<u16>,
|
||||
pub stride: usize,
|
||||
/// Allocated rows.
|
||||
pub rows: usize,
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
pub fn new(width: usize, height: usize) -> Plane {
|
||||
Plane { data: vec![0; width * height], stride: width, rows: height }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn at(&self, x: usize, y: usize) -> u16 {
|
||||
self.data[y * self.stride + x]
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn set(&mut self, x: usize, y: usize, v: u16) {
|
||||
self.data[y * self.stride + x] = v;
|
||||
}
|
||||
pub fn row(&self, y: usize) -> &[u16] {
|
||||
&self.data[y * self.stride..(y + 1) * self.stride]
|
||||
}
|
||||
pub fn row_mut(&mut self, y: usize) -> &mut [u16] {
|
||||
&mut self.data[y * self.stride..(y + 1) * self.stride]
|
||||
}
|
||||
}
|
||||
|
||||
/// A frame's three planes, sized to the macroblock-aligned coded area plus margins.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FrameBuf {
|
||||
pub planes: [Plane; 3],
|
||||
pub num_planes: usize,
|
||||
pub subsampling_x: usize,
|
||||
pub subsampling_y: usize,
|
||||
pub bit_depth: u8,
|
||||
/// Frame dimensions (luma) the samples are valid for.
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl FrameBuf {
|
||||
/// Allocate a frame for a coded area of `width` x `height` luma samples (rounded up to the
|
||||
/// 128x128 superblock grid plus 160 samples of slack for blocks extending past the edge).
|
||||
pub fn new(width: usize, height: usize, num_planes: usize, subsampling_x: usize, subsampling_y: usize, bit_depth: u8) -> FrameBuf {
|
||||
let aw = width.div_ceil(128) * 128 + 160;
|
||||
let ah = height.div_ceil(128) * 128 + 160;
|
||||
let mut planes: [Plane; 3] = Default::default();
|
||||
planes[0] = Plane::new(aw, ah);
|
||||
for p in planes.iter_mut().take(num_planes).skip(1) {
|
||||
*p = Plane::new(aw >> subsampling_x, ah >> subsampling_y);
|
||||
}
|
||||
FrameBuf { planes, num_planes, subsampling_x, subsampling_y, bit_depth, width, height }
|
||||
}
|
||||
|
||||
pub fn plane_width(&self, plane: usize) -> usize {
|
||||
if plane == 0 { self.width } else { (self.width + self.subsampling_x) >> self.subsampling_x }
|
||||
}
|
||||
|
||||
pub fn plane_height(&self, plane: usize) -> usize {
|
||||
if plane == 0 { self.height } else { (self.height + self.subsampling_y) >> self.subsampling_y }
|
||||
}
|
||||
}
|
||||
|
||||
/// A motion vector (row, col) in 1/8 sample units.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Mv {
|
||||
pub row: i16,
|
||||
pub col: i16,
|
||||
}
|
||||
|
||||
impl Mv {
|
||||
pub const ZERO: Mv = Mv { row: 0, col: 0 };
|
||||
pub fn new(row: i32, col: i32) -> Mv {
|
||||
Mv { row: row as i16, col: col as i16 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode info stored for every 4x4 luma position (the arrays the spec indexes by
|
||||
/// `[ row ][ col ]` in mode-info units).
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MiInfo {
|
||||
pub cols: usize,
|
||||
pub rows: usize,
|
||||
pub y_mode: Vec<u8>,
|
||||
pub uv_mode: Vec<u8>,
|
||||
pub ref_frame: Vec<[i8; 2]>,
|
||||
pub mv: Vec<[Mv; 2]>,
|
||||
pub is_inter: Vec<bool>,
|
||||
pub skip_mode: Vec<bool>,
|
||||
pub skip: Vec<bool>,
|
||||
pub tx_size: Vec<u8>,
|
||||
pub inter_tx_size: Vec<u8>,
|
||||
pub mi_size: Vec<u8>,
|
||||
pub segment_id: Vec<u8>,
|
||||
pub palette_size: [Vec<u8>; 2],
|
||||
pub palette_colors: [Vec<[u16; 8]>; 2],
|
||||
pub delta_lf: Vec<[i8; 4]>,
|
||||
pub comp_group_idx: Vec<u8>,
|
||||
pub compound_idx: Vec<u8>,
|
||||
pub interp_filter: Vec<[u8; 2]>,
|
||||
pub motion_mode: Vec<u8>,
|
||||
/// TxTypes[ row ][ col ] (luma 4x4 units).
|
||||
pub tx_type: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MiInfo {
|
||||
pub fn new(cols: usize, rows: usize) -> MiInfo {
|
||||
let n = cols * rows;
|
||||
MiInfo {
|
||||
cols,
|
||||
rows,
|
||||
y_mode: vec![0; n],
|
||||
uv_mode: vec![0; n],
|
||||
ref_frame: vec![[0, -1]; n],
|
||||
mv: vec![[Mv::ZERO; 2]; n],
|
||||
is_inter: vec![false; n],
|
||||
skip_mode: vec![false; n],
|
||||
skip: vec![false; n],
|
||||
tx_size: vec![0; n],
|
||||
inter_tx_size: vec![0; n],
|
||||
mi_size: vec![0; n],
|
||||
segment_id: vec![0; n],
|
||||
palette_size: [vec![0; n], vec![0; n]],
|
||||
palette_colors: [vec![[0; 8]; n], vec![[0; 8]; n]],
|
||||
delta_lf: vec![[0; 4]; n],
|
||||
comp_group_idx: vec![0; n],
|
||||
compound_idx: vec![0; n],
|
||||
interp_filter: vec![[0; 2]; n],
|
||||
motion_mode: vec![0; n],
|
||||
tx_type: vec![0; n],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn idx(&self, row: usize, col: usize) -> usize {
|
||||
row * self.cols + col
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
//! Film grain synthesis (7.18.3).
|
||||
|
||||
use crate::Picture;
|
||||
use crate::header::{FilmGrainParams, SequenceHeader};
|
||||
|
||||
pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader) {
|
||||
let _ = (pic, g, seq);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,403 @@
|
|||
//! Intra prediction (spec 7.11.2), palette prediction (7.11.4) and chroma from luma (7.11.5).
|
||||
|
||||
use crate::frame::Plane;
|
||||
use crate::spec_tables::*;
|
||||
use crate::transform::round2;
|
||||
|
||||
/// Inputs of the intra prediction process that come from the block.
|
||||
pub(crate) struct IntraParams {
|
||||
pub plane: usize,
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub have_left: bool,
|
||||
pub have_above: bool,
|
||||
pub have_above_right: bool,
|
||||
pub have_below_left: bool,
|
||||
pub mode: usize,
|
||||
pub log2w: u32,
|
||||
pub log2h: u32,
|
||||
/// Largest valid x / y in the plane: ((MiCols * MI_SIZE) >> subX) - 1.
|
||||
pub max_x: i32,
|
||||
pub max_y: i32,
|
||||
pub bit_depth: u32,
|
||||
pub angle_delta: i32,
|
||||
pub use_filter_intra: bool,
|
||||
pub filter_intra_mode: usize,
|
||||
pub enable_intra_edge_filter: bool,
|
||||
/// Result of the intra filter type process (7.11.2.8).
|
||||
pub filter_type: bool,
|
||||
}
|
||||
|
||||
const EDGE: usize = 32; // offset of index 0 in the edge buffers (they start at index -2 .. -1)
|
||||
|
||||
#[inline(always)]
|
||||
fn clip1(v: i32, bd: u32) -> u16 {
|
||||
v.clamp(0, (1 << bd) - 1) as u16
|
||||
}
|
||||
|
||||
pub(crate) fn is_directional_mode(mode: usize) -> bool {
|
||||
(V_PRED..=D67_PRED).contains(&mode)
|
||||
}
|
||||
|
||||
/// Predict the w x h block at (x, y) of `pl` (writes into the plane).
|
||||
pub(crate) fn predict_intra(pl: &mut Plane, p: &IntraParams) {
|
||||
let w = 1usize << p.log2w;
|
||||
let h = 1usize << p.log2h;
|
||||
let bd = p.bit_depth;
|
||||
let (x, y) = (p.x, p.y);
|
||||
// edge arrays with room for index -2 .. 2*(w+h)
|
||||
let mut above = [0i32; 2 * 128 + 2 * EDGE];
|
||||
let mut left = [0i32; 2 * 128 + 2 * EDGE];
|
||||
let n = w + h;
|
||||
if !p.have_above && p.have_left {
|
||||
let v = pl.at(x - 1, y) as i32;
|
||||
above[EDGE..EDGE + n].iter_mut().for_each(|a| *a = v);
|
||||
} else if !p.have_above && !p.have_left {
|
||||
let v = (1 << (bd - 1)) - 1;
|
||||
above[EDGE..EDGE + n].iter_mut().for_each(|a| *a = v);
|
||||
} else {
|
||||
let above_limit = (p.max_x).min(x as i32 + if p.have_above_right { 2 * w as i32 } else { w as i32 } - 1);
|
||||
let row = pl.row(y - 1);
|
||||
for i in 0..n {
|
||||
above[EDGE + i] = row[(above_limit.min(x as i32 + i as i32)) as usize] as i32;
|
||||
}
|
||||
}
|
||||
if !p.have_left && p.have_above {
|
||||
let v = pl.at(x, y - 1) as i32;
|
||||
left[EDGE..EDGE + n].iter_mut().for_each(|a| *a = v);
|
||||
} else if !p.have_left && !p.have_above {
|
||||
let v = (1 << (bd - 1)) + 1;
|
||||
left[EDGE..EDGE + n].iter_mut().for_each(|a| *a = v);
|
||||
} else {
|
||||
let left_limit = (p.max_y).min(y as i32 + if p.have_below_left { 2 * h as i32 } else { h as i32 } - 1);
|
||||
for i in 0..n {
|
||||
left[EDGE + i] = pl.at(x - 1, left_limit.min(y as i32 + i as i32) as usize) as i32;
|
||||
}
|
||||
}
|
||||
let corner = if p.have_above && p.have_left {
|
||||
pl.at(x - 1, y - 1) as i32
|
||||
} else if p.have_above {
|
||||
pl.at(x, y - 1) as i32
|
||||
} else if p.have_left {
|
||||
pl.at(x - 1, y) as i32
|
||||
} else {
|
||||
1 << (bd - 1)
|
||||
};
|
||||
above[EDGE - 1] = corner;
|
||||
left[EDGE - 1] = corner;
|
||||
|
||||
let mut pred = [0u16; 64 * 64];
|
||||
if p.plane == 0 && p.use_filter_intra {
|
||||
recursive_intra(&above, &left, w, h, p.filter_intra_mode, bd, &mut pred);
|
||||
} else if is_directional_mode(p.mode) {
|
||||
directional(&mut above, &mut left, p, w, h, &mut pred);
|
||||
} else if p.mode == SMOOTH_PRED || p.mode == SMOOTH_V_PRED || p.mode == SMOOTH_H_PRED {
|
||||
smooth(&above, &left, p.mode, p.log2w, p.log2h, w, h, &mut pred);
|
||||
} else if p.mode == DC_PRED {
|
||||
dc(&above, &left, p.have_left, p.have_above, p.log2w, p.log2h, w, h, bd, &mut pred);
|
||||
} else {
|
||||
// PAETH_PRED
|
||||
let tl = above[EDGE - 1];
|
||||
for i in 0..h {
|
||||
for j in 0..w {
|
||||
let a = above[EDGE + j];
|
||||
let l = left[EDGE + i];
|
||||
let base = a + l - tl;
|
||||
let p_left = (base - l).abs();
|
||||
let p_top = (base - a).abs();
|
||||
let p_tl = (base - tl).abs();
|
||||
pred[i * w + j] = if p_left <= p_top && p_left <= p_tl {
|
||||
l as u16
|
||||
} else if p_top <= p_tl {
|
||||
a as u16
|
||||
} else {
|
||||
tl as u16
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..h {
|
||||
pl.row_mut(y + i)[x..x + w].copy_from_slice(&pred[i * w..i * w + w]);
|
||||
}
|
||||
}
|
||||
|
||||
fn recursive_intra(above: &[i32], left: &[i32], w: usize, h: usize, mode: usize, bd: u32, pred: &mut [u16]) {
|
||||
let w4 = w >> 2;
|
||||
let h2 = h >> 1;
|
||||
for i2 in 0..h2 {
|
||||
for j4 in 0..w4 {
|
||||
let mut pv = [0i32; 7];
|
||||
for (i, v) in pv.iter_mut().enumerate() {
|
||||
*v = if i < 5 {
|
||||
if i2 == 0 {
|
||||
above[EDGE + (j4 << 2) + i - 1]
|
||||
} else if j4 == 0 && i == 0 {
|
||||
left[EDGE + (i2 << 1) - 1]
|
||||
} else {
|
||||
pred[((i2 << 1) - 1) * w + (j4 << 2) + i - 1] as i32
|
||||
}
|
||||
} else if j4 == 0 {
|
||||
left[EDGE + (i2 << 1) + i - 5]
|
||||
} else {
|
||||
pred[((i2 << 1) + i - 5) * w + (j4 << 2) - 1] as i32
|
||||
};
|
||||
}
|
||||
for i1 in 0..2 {
|
||||
for j1 in 0..4 {
|
||||
let mut pr = 0i32;
|
||||
for i in 0..7 {
|
||||
pr += INTRA_FILTER_TAPS[mode][(i1 << 2) + j1][i] as i32 * pv[i];
|
||||
}
|
||||
pred[((i2 << 1) + i1) * w + (j4 << 2) + j1] = clip1(round2_signed(pr, INTRA_FILTER_SCALE_BITS as u32), bd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn round2_signed(x: i32, n: u32) -> i32 {
|
||||
if x >= 0 { round2(x, n) } else { -round2(-x, n) }
|
||||
}
|
||||
|
||||
fn sm_weights(log2: u32) -> &'static [u8] {
|
||||
match log2 {
|
||||
2 => &SM_WEIGHTS_TX_4X4,
|
||||
3 => &SM_WEIGHTS_TX_8X8,
|
||||
4 => &SM_WEIGHTS_TX_16X16,
|
||||
5 => &SM_WEIGHTS_TX_32X32,
|
||||
_ => &SM_WEIGHTS_TX_64X64,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn smooth(above: &[i32], left: &[i32], mode: usize, log2w: u32, log2h: u32, w: usize, h: usize, pred: &mut [u16]) {
|
||||
let wx = sm_weights(log2w);
|
||||
let wy = sm_weights(log2h);
|
||||
for i in 0..h {
|
||||
for j in 0..w {
|
||||
let v = if mode == SMOOTH_PRED {
|
||||
let s = wy[i] as i32 * above[EDGE + j]
|
||||
+ (256 - wy[i] as i32) * left[EDGE + h - 1]
|
||||
+ wx[j] as i32 * left[EDGE + i]
|
||||
+ (256 - wx[j] as i32) * above[EDGE + w - 1];
|
||||
round2(s, 9)
|
||||
} else if mode == SMOOTH_V_PRED {
|
||||
round2(wy[i] as i32 * above[EDGE + j] + (256 - wy[i] as i32) * left[EDGE + h - 1], 8)
|
||||
} else {
|
||||
round2(wx[j] as i32 * left[EDGE + i] + (256 - wx[j] as i32) * above[EDGE + w - 1], 8)
|
||||
};
|
||||
pred[i * w + j] = v as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn dc(above: &[i32], left: &[i32], have_left: bool, have_above: bool, log2w: u32, log2h: u32, w: usize, h: usize, bd: u32, pred: &mut [u16]) {
|
||||
let v = if have_left && have_above {
|
||||
let mut sum: i32 = left[EDGE..EDGE + h].iter().sum::<i32>() + above[EDGE..EDGE + w].iter().sum::<i32>();
|
||||
sum += ((w + h) >> 1) as i32;
|
||||
sum / (w + h) as i32
|
||||
} else if have_left {
|
||||
let sum: i32 = left[EDGE..EDGE + h].iter().sum();
|
||||
(sum + (h >> 1) as i32) >> log2h
|
||||
} else if have_above {
|
||||
let sum: i32 = above[EDGE..EDGE + w].iter().sum();
|
||||
(sum + (w >> 1) as i32) >> log2w
|
||||
} else {
|
||||
1 << (bd - 1)
|
||||
};
|
||||
let v = clip1(v, bd);
|
||||
pred[..w * h].iter_mut().for_each(|p| *p = v);
|
||||
}
|
||||
|
||||
fn edge_filter_strength(w: usize, h: usize, filter_type: bool, delta: i32) -> u32 {
|
||||
let d = delta.abs();
|
||||
let blk_wh = w + h;
|
||||
let mut strength = 0;
|
||||
if !filter_type {
|
||||
if blk_wh <= 8 {
|
||||
if d >= 56 {
|
||||
strength = 1;
|
||||
}
|
||||
} else if blk_wh <= 16 {
|
||||
if d >= 40 {
|
||||
strength = 1;
|
||||
}
|
||||
} else if blk_wh <= 24 {
|
||||
if d >= 8 {
|
||||
strength = 1;
|
||||
}
|
||||
if d >= 16 {
|
||||
strength = 2;
|
||||
}
|
||||
if d >= 32 {
|
||||
strength = 3;
|
||||
}
|
||||
} else if blk_wh <= 32 {
|
||||
strength = 1;
|
||||
if d >= 4 {
|
||||
strength = 2;
|
||||
}
|
||||
if d >= 32 {
|
||||
strength = 3;
|
||||
}
|
||||
} else {
|
||||
strength = 3;
|
||||
}
|
||||
} else if blk_wh <= 8 {
|
||||
if d >= 40 {
|
||||
strength = 1;
|
||||
}
|
||||
if d >= 64 {
|
||||
strength = 2;
|
||||
}
|
||||
} else if blk_wh <= 16 {
|
||||
if d >= 20 {
|
||||
strength = 1;
|
||||
}
|
||||
if d >= 48 {
|
||||
strength = 2;
|
||||
}
|
||||
} else if blk_wh <= 24 {
|
||||
if d >= 4 {
|
||||
strength = 3;
|
||||
}
|
||||
} else {
|
||||
strength = 3;
|
||||
}
|
||||
strength
|
||||
}
|
||||
|
||||
fn use_upsample(w: usize, h: usize, filter_type: bool, delta: i32) -> bool {
|
||||
let d = delta.abs();
|
||||
let blk_wh = w + h;
|
||||
if d <= 0 || d >= 40 {
|
||||
false
|
||||
} else if !filter_type {
|
||||
blk_wh <= 16
|
||||
} else {
|
||||
blk_wh <= 8
|
||||
}
|
||||
}
|
||||
|
||||
/// Intra edge filter (7.11.2.12) on buf[-1 .. sz-2] (edge[i] = buf[i - 1]).
|
||||
fn edge_filter(buf: &mut [i32], sz: usize, strength: u32) {
|
||||
if strength == 0 {
|
||||
return;
|
||||
}
|
||||
let mut edge = [0i32; 2 * 128 + 2 * EDGE];
|
||||
for i in 0..sz {
|
||||
edge[i] = buf[EDGE + i - 1];
|
||||
}
|
||||
for i in 1..sz {
|
||||
let mut s = 0;
|
||||
for j in 0..INTRA_EDGE_TAPS {
|
||||
let k = (i as i32 - 2 + j as i32).clamp(0, sz as i32 - 1) as usize;
|
||||
s += INTRA_EDGE_KERNEL[strength as usize - 1][j] as i32 * edge[k];
|
||||
}
|
||||
buf[EDGE + i - 1] = (s + 8) >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// Intra edge upsample (7.11.2.11).
|
||||
fn edge_upsample(buf: &mut [i32], num_px: usize, bd: u32) {
|
||||
let mut dup = [0i32; 2 * 128 + 8];
|
||||
dup[0] = buf[EDGE - 1];
|
||||
for i in -1..num_px as i32 {
|
||||
dup[(i + 2) as usize] = buf[(EDGE as i32 + i) as usize];
|
||||
}
|
||||
dup[num_px + 2] = buf[EDGE + num_px - 1];
|
||||
buf[EDGE - 2] = dup[0];
|
||||
for i in 0..num_px {
|
||||
let s = -dup[i] + 9 * dup[i + 1] + 9 * dup[i + 2] - dup[i + 3];
|
||||
let s = clip1(round2(s, 4), bd) as i32;
|
||||
buf[EDGE + 2 * i - 1] = s;
|
||||
buf[EDGE + 2 * i] = dup[i + 2];
|
||||
}
|
||||
}
|
||||
|
||||
fn directional(above: &mut [i32], left: &mut [i32], p: &IntraParams, w: usize, h: usize, pred: &mut [u16]) {
|
||||
let p_angle = MODE_TO_ANGLE[p.mode] as i32 + p.angle_delta * ANGLE_STEP as i32;
|
||||
let mut upsample_above = 0u32;
|
||||
let mut upsample_left = 0u32;
|
||||
if p.enable_intra_edge_filter {
|
||||
if p_angle != 90 && p_angle != 180 {
|
||||
if p_angle > 90 && p_angle < 180 && (w + h) >= 24 {
|
||||
let s = left[EDGE] * 5 + above[EDGE - 1] * 6 + above[EDGE] * 5;
|
||||
let c = round2(s, 4);
|
||||
left[EDGE - 1] = c;
|
||||
above[EDGE - 1] = c;
|
||||
}
|
||||
if p.have_above {
|
||||
let strength = edge_filter_strength(w, h, p.filter_type, p_angle - 90);
|
||||
let num_px = w.min((p.max_x - p.x as i32 + 1) as usize) + if p_angle < 90 { h } else { 0 } + 1;
|
||||
edge_filter(above, num_px, strength);
|
||||
}
|
||||
if p.have_left {
|
||||
let strength = edge_filter_strength(w, h, p.filter_type, p_angle - 180);
|
||||
let num_px = h.min((p.max_y - p.y as i32 + 1) as usize) + if p_angle > 180 { w } else { 0 } + 1;
|
||||
edge_filter(left, num_px, strength);
|
||||
}
|
||||
}
|
||||
if use_upsample(w, h, p.filter_type, p_angle - 90) {
|
||||
upsample_above = 1;
|
||||
let num_px = w + if p_angle < 90 { h } else { 0 };
|
||||
edge_upsample(above, num_px, p.bit_depth);
|
||||
}
|
||||
if use_upsample(w, h, p.filter_type, p_angle - 180) {
|
||||
upsample_left = 1;
|
||||
let num_px = h + if p_angle > 180 { w } else { 0 };
|
||||
edge_upsample(left, num_px, p.bit_depth);
|
||||
}
|
||||
}
|
||||
let dx = if p_angle < 90 {
|
||||
DR_INTRA_DERIVATIVE[p_angle as usize] as i32
|
||||
} else if p_angle > 90 && p_angle < 180 {
|
||||
DR_INTRA_DERIVATIVE[(180 - p_angle) as usize] as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let dy = if p_angle > 90 && p_angle < 180 {
|
||||
DR_INTRA_DERIVATIVE[(p_angle - 90) as usize] as i32
|
||||
} else if p_angle > 180 {
|
||||
DR_INTRA_DERIVATIVE[(270 - p_angle) as usize] as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let a = |i: i32| above[(EDGE as i32 + i) as usize];
|
||||
let l = |i: i32| left[(EDGE as i32 + i) as usize];
|
||||
for i in 0..h as i32 {
|
||||
for j in 0..w as i32 {
|
||||
let v = if p_angle < 90 {
|
||||
let idx = (i + 1) * dx;
|
||||
let base = (idx >> (6 - upsample_above)) + (j << upsample_above);
|
||||
let shift = ((idx << upsample_above) >> 1) & 0x1f;
|
||||
let max_base_x = (w as i32 + h as i32 - 1) << upsample_above;
|
||||
if base < max_base_x { round2(a(base) * (32 - shift) + a(base + 1) * shift, 5) } else { a(max_base_x) }
|
||||
} else if p_angle > 90 && p_angle < 180 {
|
||||
let idx = (j << 6) - (i + 1) * dx;
|
||||
let base = idx >> (6 - upsample_above);
|
||||
if base >= -(1 << upsample_above) {
|
||||
let shift = ((idx << upsample_above) >> 1) & 0x1f;
|
||||
round2(a(base) * (32 - shift) + a(base + 1) * shift, 5)
|
||||
} else {
|
||||
let idx = (i << 6) - (j + 1) * dy;
|
||||
let base = idx >> (6 - upsample_left);
|
||||
let shift = ((idx << upsample_left) >> 1) & 0x1f;
|
||||
round2(l(base) * (32 - shift) + l(base + 1) * shift, 5)
|
||||
}
|
||||
} else if p_angle > 180 {
|
||||
let idx = (j + 1) * dy;
|
||||
let base = (idx >> (6 - upsample_left)) + (i << upsample_left);
|
||||
let shift = ((idx << upsample_left) >> 1) & 0x1f;
|
||||
round2(l(base) * (32 - shift) + l(base + 1) * shift, 5)
|
||||
} else if p_angle == 90 {
|
||||
a(j)
|
||||
} else {
|
||||
l(i)
|
||||
};
|
||||
pred[(i as usize) * w + j as usize] = v as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
//! Clean-room AV1 decoder, implemented from the AV1 Bitstream & Decoding Process Specification
|
||||
//! v1.0.0 with Errata 1 (AOMediaCodec/av1-spec).
|
||||
//!
|
||||
//! See the crate README for the conformance status of each decoding stage.
|
||||
|
||||
// Items used only by stages still being written (inter prediction, post filters).
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod bits;
|
||||
mod cdf;
|
||||
mod decoder;
|
||||
mod frame;
|
||||
mod grain;
|
||||
pub mod header;
|
||||
mod intra;
|
||||
mod postfilter;
|
||||
mod spec_tables;
|
||||
mod state;
|
||||
mod symbol;
|
||||
mod tile;
|
||||
mod transform;
|
||||
|
||||
pub use decoder::Decoder;
|
||||
pub use header::{ColorConfig, FrameHeader, SequenceHeader};
|
||||
|
||||
/// Decoder errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("truncated AV1 data")]
|
||||
Truncated,
|
||||
#[error("invalid AV1 data: {0}")]
|
||||
Invalid(&'static str),
|
||||
#[error("unsupported AV1 feature: {0}")]
|
||||
Unsupported(&'static str),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A decoded (shown) frame: planar samples without row padding, `bit_depth` significant bits in
|
||||
/// `u16`. Chroma planes are `((width + ssx) >> ssx) x ((height + ssy) >> ssy)`; they are empty
|
||||
/// for monochrome streams.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Picture {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub bit_depth: u8,
|
||||
pub subsampling_x: u8,
|
||||
pub subsampling_y: u8,
|
||||
pub mono_chrome: bool,
|
||||
pub planes: [Vec<u16>; 3],
|
||||
pub color_primaries: u8,
|
||||
pub transfer_characteristics: u8,
|
||||
pub matrix_coefficients: u8,
|
||||
pub full_range: bool,
|
||||
}
|
||||
|
||||
impl Picture {
|
||||
pub fn plane_width(&self, plane: usize) -> u32 {
|
||||
if plane == 0 { self.width } else { (self.width + self.subsampling_x as u32) >> self.subsampling_x }
|
||||
}
|
||||
pub fn plane_height(&self, plane: usize) -> u32 {
|
||||
if plane == 0 { self.height } else { (self.height + self.subsampling_y as u32) >> self.subsampling_y }
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a temporal unit starts a random access point: it contains a sequence header and a
|
||||
/// shown key frame (used for keyframe detection by demuxers without sync-sample tables).
|
||||
pub fn is_key_frame_unit(data: &[u8]) -> bool {
|
||||
let mut pos = 0;
|
||||
let mut seq_seen = false;
|
||||
while pos < data.len() {
|
||||
let h = data[pos];
|
||||
let obu_type = (h >> 3) & 0xf;
|
||||
let ext = h & 4 != 0;
|
||||
let has_size = h & 2 != 0;
|
||||
let mut p = pos + 1 + ext as usize;
|
||||
let size = if has_size {
|
||||
match bits::leb128(data.get(p..).unwrap_or(&[])) {
|
||||
Ok((v, n)) => {
|
||||
p += n;
|
||||
v as usize
|
||||
}
|
||||
Err(_) => return false,
|
||||
}
|
||||
} else {
|
||||
data.len().saturating_sub(p)
|
||||
};
|
||||
if p > data.len() {
|
||||
return false;
|
||||
}
|
||||
match obu_type {
|
||||
1 => seq_seen = true,
|
||||
3 | 6 => {
|
||||
let b = data.get(p).copied().unwrap_or(0);
|
||||
// show_existing_frame = 0, frame_type = KEY_FRAME (0), show_frame = 1
|
||||
let show_existing = b >> 7;
|
||||
let frame_type = (b >> 5) & 3;
|
||||
let show_frame = (b >> 4) & 1;
|
||||
return seq_seen && show_existing == 0 && frame_type == 0 && show_frame == 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pos = p.saturating_add(size);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
//! In-loop post filters run by decode_frame_wrapup: loop filter (7.14), CDEF (7.15),
|
||||
//! super-resolution upscaling (7.16) and loop restoration (7.17).
|
||||
|
||||
use crate::state::FrameState;
|
||||
|
||||
pub(crate) fn apply(fs: &mut FrameState) {
|
||||
let _ = fs;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,130 @@
|
|||
//! Per-frame decoding state shared by the tiles and the post-filters.
|
||||
|
||||
use crate::frame::{FrameBuf, MiInfo};
|
||||
use crate::header::{FrameHeader, SequenceHeader};
|
||||
use crate::spec_tables::*;
|
||||
|
||||
pub(crate) struct FrameState {
|
||||
pub seq: SequenceHeader,
|
||||
pub fh: FrameHeader,
|
||||
pub num_planes: usize,
|
||||
pub ssx: usize,
|
||||
pub ssy: usize,
|
||||
pub bit_depth: u32,
|
||||
pub cur: FrameBuf,
|
||||
pub mi: MiInfo,
|
||||
pub above_level: [Vec<u8>; 3],
|
||||
pub above_dc: [Vec<u8>; 3],
|
||||
pub above_seg_pred: Vec<u8>,
|
||||
pub left_level: [Vec<u8>; 3],
|
||||
pub left_dc: [Vec<u8>; 3],
|
||||
pub left_seg_pred: Vec<u8>,
|
||||
/// cdef_idx per 64x64 block.
|
||||
cdef: Vec<i8>,
|
||||
cdef_cols: usize,
|
||||
/// LoopfilterTxSizes[ plane ][ row ][ col ] in 4x4 units of each plane.
|
||||
pub lf_tx_size: [Vec<u8>; 3],
|
||||
pub lr_unit_rows: [usize; 3],
|
||||
pub lr_unit_cols: [usize; 3],
|
||||
pub lr_type: [Vec<u8>; 3],
|
||||
pub lr_wiener: [Vec<[[i8; 3]; 2]>; 3],
|
||||
pub lr_sgr_set: [Vec<u8>; 3],
|
||||
pub lr_sgr_xqd: [Vec<[i8; 2]>; 3],
|
||||
/// TileIntraFrameYModeCdf (reset per tile).
|
||||
pub intra_frame_y_mode_cdf: [[[u16; 14]; 5]; 5],
|
||||
}
|
||||
|
||||
fn count_units_in_frame(unit_size: usize, frame_size: usize) -> usize {
|
||||
((frame_size + (unit_size >> 1)) / unit_size).max(1)
|
||||
}
|
||||
|
||||
impl FrameState {
|
||||
pub fn new(seq: &SequenceHeader, fh: &FrameHeader) -> FrameState {
|
||||
let c = &seq.color;
|
||||
let (ssx, ssy) = (c.subsampling_x as usize, c.subsampling_y as usize);
|
||||
let mi_cols = fh.mi_cols as usize;
|
||||
let mi_rows = fh.mi_rows as usize;
|
||||
let cur = FrameBuf::new(fh.frame_width as usize, fh.frame_height as usize, c.num_planes, ssx, ssy, c.bit_depth);
|
||||
let cdef_cols = mi_cols.div_ceil(16) + 2;
|
||||
let cdef_rows = mi_rows.div_ceil(16) + 2;
|
||||
let mut lr_unit_rows = [0; 3];
|
||||
let mut lr_unit_cols = [0; 3];
|
||||
let mut lr_type: [Vec<u8>; 3] = Default::default();
|
||||
let mut lr_wiener: [Vec<[[i8; 3]; 2]>; 3] = Default::default();
|
||||
let mut lr_sgr_set: [Vec<u8>; 3] = Default::default();
|
||||
let mut lr_sgr_xqd: [Vec<[i8; 2]>; 3] = Default::default();
|
||||
for plane in 0..c.num_planes {
|
||||
if fh.lr.frame_restoration_type[plane] == RESTORE_NONE as u8 {
|
||||
continue;
|
||||
}
|
||||
let sub_x = if plane == 0 { 0 } else { ssx };
|
||||
let sub_y = if plane == 0 { 0 } else { ssy };
|
||||
let unit = fh.lr.loop_restoration_size[plane] as usize;
|
||||
lr_unit_rows[plane] = count_units_in_frame(unit, (fh.frame_height as usize + sub_y) >> sub_y);
|
||||
lr_unit_cols[plane] = count_units_in_frame(unit, (fh.upscaled_width as usize + sub_x) >> sub_x);
|
||||
let n = lr_unit_rows[plane] * lr_unit_cols[plane];
|
||||
lr_type[plane] = vec![RESTORE_NONE as u8; n];
|
||||
lr_wiener[plane] = vec![[[0; 3]; 2]; n];
|
||||
lr_sgr_set[plane] = vec![0; n];
|
||||
lr_sgr_xqd[plane] = vec![[0; 2]; n];
|
||||
}
|
||||
FrameState {
|
||||
seq: seq.clone(),
|
||||
fh: fh.clone(),
|
||||
num_planes: c.num_planes,
|
||||
ssx,
|
||||
ssy,
|
||||
bit_depth: c.bit_depth as u32,
|
||||
cur,
|
||||
mi: MiInfo::new(mi_cols, mi_rows),
|
||||
above_level: std::array::from_fn(|_| vec![0; mi_cols + 64]),
|
||||
above_dc: std::array::from_fn(|_| vec![0; mi_cols + 64]),
|
||||
above_seg_pred: vec![0; mi_cols + 64],
|
||||
left_level: std::array::from_fn(|_| vec![0; mi_rows + 64]),
|
||||
left_dc: std::array::from_fn(|_| vec![0; mi_rows + 64]),
|
||||
left_seg_pred: vec![0; mi_rows + 64],
|
||||
cdef: vec![-1; cdef_cols * cdef_rows],
|
||||
cdef_cols,
|
||||
lf_tx_size: std::array::from_fn(|_| vec![0; (mi_cols + 32) * (mi_rows + 32)]),
|
||||
lr_unit_rows,
|
||||
lr_unit_cols,
|
||||
lr_type,
|
||||
lr_wiener,
|
||||
lr_sgr_set,
|
||||
lr_sgr_xqd,
|
||||
intra_frame_y_mode_cdf: DEFAULT_INTRA_FRAME_Y_MODE_CDF,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plane_residual_size(&self, subsize: usize, plane: usize) -> usize {
|
||||
let sx = if plane > 0 { self.ssx } else { 0 };
|
||||
let sy = if plane > 0 { self.ssy } else { 0 };
|
||||
SUBSAMPLED_SIZE[subsize][sx][sy] as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cdef_idx(&self, r: usize, c: usize) -> i8 {
|
||||
self.cdef[(r >> 4) * self.cdef_cols + (c >> 4)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cdef_idx(&mut self, r: usize, c: usize, v: i8) {
|
||||
let i = (r >> 4) * self.cdef_cols + (c >> 4);
|
||||
if i < self.cdef.len() {
|
||||
self.cdef[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lf_tx_size(&self, plane: usize, row: usize, col: usize) -> usize {
|
||||
self.lf_tx_size[plane][row * (self.mi.cols + 32) + col] as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_lf_tx_size(&mut self, plane: usize, row: usize, col: usize, v: u8) {
|
||||
let stride = self.mi.cols + 32;
|
||||
if col < stride && row < self.mi.rows + 32 {
|
||||
self.lf_tx_size[plane][row * stride + col] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
//! Symbol (arithmetic) decoder, spec 8.2.
|
||||
//!
|
||||
//! The state follows the specification literally: `SymbolValue` / `SymbolRange` are 15/16-bit
|
||||
//! quantities renormalised by reading `bits` new bits at a time, and `SymbolMaxBits` counts the
|
||||
//! bits still available (going negative while zero padding is consumed).
|
||||
|
||||
const EC_PROB_SHIFT: u32 = 6;
|
||||
const EC_MIN_PROB: u32 = 4;
|
||||
|
||||
pub(crate) struct SymbolDecoder<'a> {
|
||||
data: &'a [u8],
|
||||
/// Bit position of the next bit to read.
|
||||
pos: usize,
|
||||
end: usize,
|
||||
value: u32,
|
||||
range: u32,
|
||||
max_bits: i64,
|
||||
pub disable_update: bool,
|
||||
}
|
||||
|
||||
impl<'a> SymbolDecoder<'a> {
|
||||
/// `init_symbol( sz )` over `data` (the tile's bytes).
|
||||
pub fn new(data: &'a [u8], disable_update: bool) -> Self {
|
||||
let sz = data.len();
|
||||
let mut d = SymbolDecoder { data, pos: 0, end: sz * 8, value: 0, range: 1 << 15, max_bits: 8 * sz as i64 - 15, disable_update };
|
||||
let num_bits = (sz * 8).min(15) as u32;
|
||||
let buf = d.read_bits(num_bits);
|
||||
let padded = buf << (15 - num_bits);
|
||||
d.value = ((1 << 15) - 1) ^ padded;
|
||||
d
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_bits(&mut self, n: u32) -> u32 {
|
||||
let mut x = 0u32;
|
||||
for _ in 0..n {
|
||||
let bit = if self.pos < self.end { (self.data[self.pos >> 3] >> (7 - (self.pos & 7))) & 1 } else { 0 };
|
||||
self.pos += 1;
|
||||
x = (x << 1) | bit as u32;
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
/// `read_symbol( cdf )`: `cdf` has N + 1 entries (the last is the adaptation counter).
|
||||
#[inline]
|
||||
pub fn read_symbol(&mut self, cdf: &mut [u16]) -> usize {
|
||||
let n = cdf.len() - 1;
|
||||
let symbol = self.decode(cdf, n);
|
||||
if !self.disable_update {
|
||||
update_cdf(cdf, n, symbol);
|
||||
}
|
||||
symbol
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn decode(&mut self, cdf: &[u16], n: usize) -> usize {
|
||||
let mut cur = self.range;
|
||||
let mut symbol = 0usize;
|
||||
let mut prev;
|
||||
loop {
|
||||
prev = cur;
|
||||
let f = (1u32 << 15) - cdf[symbol] as u32;
|
||||
cur = ((self.range >> 8) * (f >> EC_PROB_SHIFT)) >> (7 - EC_PROB_SHIFT);
|
||||
cur += EC_MIN_PROB * (n - symbol - 1) as u32;
|
||||
if self.value >= cur {
|
||||
break;
|
||||
}
|
||||
symbol += 1;
|
||||
}
|
||||
self.range = prev - cur;
|
||||
self.value -= cur;
|
||||
self.renormalize();
|
||||
symbol
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn renormalize(&mut self) {
|
||||
let bits = 15 - (31 - self.range.leading_zeros());
|
||||
if bits == 0 {
|
||||
return;
|
||||
}
|
||||
self.range <<= bits;
|
||||
let num_bits = (bits as i64).min(self.max_bits.max(0)) as u32;
|
||||
let new_data = self.read_bits(num_bits);
|
||||
let padded = new_data << (bits - num_bits);
|
||||
self.value = padded ^ (((self.value + 1) << bits) - 1);
|
||||
self.max_bits -= bits as i64;
|
||||
}
|
||||
|
||||
/// `read_bool()`
|
||||
#[inline]
|
||||
pub fn read_bool(&mut self) -> u32 {
|
||||
let cdf = [1u16 << 14, 1 << 15, 0];
|
||||
self.decode(&cdf, 2) as u32
|
||||
}
|
||||
|
||||
/// `read_literal( n )` (L(n))
|
||||
pub fn read_literal(&mut self, n: u32) -> u32 {
|
||||
let mut x = 0;
|
||||
for _ in 0..n {
|
||||
x = (x << 1) | self.read_bool();
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
/// NS(n)
|
||||
pub fn read_ns(&mut self, n: u32) -> u32 {
|
||||
if n <= 1 {
|
||||
return 0;
|
||||
}
|
||||
let w = 32 - n.leading_zeros();
|
||||
let m = (1u32 << w) - n;
|
||||
let v = self.read_literal(w - 1);
|
||||
if v < m {
|
||||
return v;
|
||||
}
|
||||
let extra = self.read_literal(1);
|
||||
(v << 1) - m + extra
|
||||
}
|
||||
|
||||
/// SymbolMaxBits (for the exit process checks).
|
||||
pub fn max_bits(&self) -> i64 {
|
||||
self.max_bits
|
||||
}
|
||||
}
|
||||
|
||||
/// CDF adaptation (8.2.6).
|
||||
#[inline(always)]
|
||||
pub(crate) fn update_cdf(cdf: &mut [u16], n: usize, symbol: usize) {
|
||||
let count = cdf[n];
|
||||
let rate = 3 + (count > 15) as u32 + (count > 31) as u32 + (31 - (n as u32).leading_zeros()).min(2);
|
||||
let mut tmp = 0u32;
|
||||
for i in 0..n - 1 {
|
||||
if i == symbol {
|
||||
tmp = 1 << 15;
|
||||
}
|
||||
let c = cdf[i] as u32;
|
||||
if tmp < c {
|
||||
cdf[i] = (c - ((c - tmp) >> rate)) as u16;
|
||||
} else {
|
||||
cdf[i] = (c + ((tmp - c) >> rate)) as u16;
|
||||
}
|
||||
}
|
||||
cdf[n] += (count < 32) as u16;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,540 @@
|
|||
//! Inverse transforms (spec 7.13), implemented step by step as specified.
|
||||
|
||||
use crate::spec_tables::*;
|
||||
|
||||
#[inline(always)]
|
||||
fn round2_64(x: i64, n: u32) -> i32 {
|
||||
if n == 0 { x as i32 } else { ((x + (1i64 << (n - 1))) >> n) as i32 }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn round2(x: i32, n: u32) -> i32 {
|
||||
if n == 0 { x } else { (x + (1 << (n - 1))) >> n }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cos128(angle: i32) -> i64 {
|
||||
let a = (angle & 255) as usize;
|
||||
(match a {
|
||||
0..=64 => COS128_LOOKUP[a] as i32,
|
||||
65..=128 => -(COS128_LOOKUP[128 - a] as i32),
|
||||
129..=192 => -(COS128_LOOKUP[a - 128] as i32),
|
||||
_ => COS128_LOOKUP[256 - a] as i32,
|
||||
}) as i64
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sin128(angle: i32) -> i64 {
|
||||
cos128(angle - 64)
|
||||
}
|
||||
|
||||
fn brev(num_bits: u32, x: usize) -> usize {
|
||||
let mut t = 0;
|
||||
for i in 0..num_bits {
|
||||
let bit = (x >> i) & 1;
|
||||
t += bit << (num_bits - 1 - i);
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
/// Butterfly rotation B( a, b, angle, flip ).
|
||||
#[inline(always)]
|
||||
fn bf(t: &mut [i32], a: usize, b: usize, angle: i32, flip: bool) {
|
||||
let x = t[a] as i64 * cos128(angle) - t[b] as i64 * sin128(angle);
|
||||
let y = t[a] as i64 * sin128(angle) + t[b] as i64 * cos128(angle);
|
||||
t[a] = round2_64(x, 12);
|
||||
t[b] = round2_64(y, 12);
|
||||
if flip {
|
||||
t.swap(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hadamard rotation H( a, b, flip, r ).
|
||||
#[inline(always)]
|
||||
fn hd(t: &mut [i32], a: usize, b: usize, flip: bool, r: u32) {
|
||||
let (a, b) = if flip { (b, a) } else { (a, b) };
|
||||
let x = t[a];
|
||||
let y = t[b];
|
||||
let lo = -(1i32 << (r - 1));
|
||||
let hi = (1i32 << (r - 1)) - 1;
|
||||
t[a] = (x + y).clamp(lo, hi);
|
||||
t[b] = (x - y).clamp(lo, hi);
|
||||
}
|
||||
|
||||
/// Inverse DCT of length 2^n (7.13.2.3), in place.
|
||||
pub(crate) fn inverse_dct(t: &mut [i32], n: u32, r: u32) {
|
||||
// 7.13.2.2 permutation
|
||||
let len = 1usize << n;
|
||||
let mut copy = [0i32; 64];
|
||||
copy[..len].copy_from_slice(&t[..len]);
|
||||
for i in 0..len {
|
||||
t[i] = copy[brev(n, i)];
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..16 {
|
||||
bf(t, 32 + i, 63 - i, 63 - 4 * brev(4, i) as i32, false);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..8 {
|
||||
bf(t, 16 + i, 31 - i, 6 + ((brev(3, 7 - i) as i32) << 3), false);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..16 {
|
||||
hd(t, 32 + i * 2, 33 + i * 2, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..4 {
|
||||
bf(t, 8 + i, 15 - i, 12 + ((brev(2, 3 - i) as i32) << 4), false);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..8 {
|
||||
hd(t, 16 + 2 * i, 17 + 2 * i, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..4 {
|
||||
for j in 0..2 {
|
||||
bf(t, 62 - i * 4 - j, 33 + i * 4 + j, 60 - 16 * brev(2, i) as i32 + 64 * j as i32, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n >= 3 {
|
||||
for i in 0..2 {
|
||||
bf(t, 4 + i, 7 - i, 56 - 32 * i as i32, false);
|
||||
}
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..4 {
|
||||
hd(t, 8 + 2 * i, 9 + 2 * i, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
bf(t, 30 - 4 * i - j, 17 + 4 * i + j, 24 + ((j as i32) << 6) + (((1 - i) as i32) << 5), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..8 {
|
||||
for j in 0..2 {
|
||||
hd(t, 32 + i * 4 + j, 35 + i * 4 - j, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..2 {
|
||||
bf(t, 2 * i, 2 * i + 1, 32 + 16 * i as i32, i == 0);
|
||||
}
|
||||
if n >= 3 {
|
||||
for i in 0..2 {
|
||||
hd(t, 4 + 2 * i, 5 + 2 * i, i == 1, r);
|
||||
}
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..2 {
|
||||
bf(t, 14 - i, 9 + i, 48 + 64 * i as i32, true);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..4 {
|
||||
for j in 0..2 {
|
||||
hd(t, 16 + 4 * i + j, 19 + 4 * i - j, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..2 {
|
||||
for j in 0..4 {
|
||||
bf(t, 61 - i * 8 - j, 34 + i * 8 + j, 56 - i as i32 * 32 + (j as i32 >> 1) * 64, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..2 {
|
||||
hd(t, i, 3 - i, false, r);
|
||||
}
|
||||
if n >= 3 {
|
||||
bf(t, 6, 5, 32, true);
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
hd(t, 8 + 4 * i + j, 11 + 4 * i - j, i == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..4 {
|
||||
bf(t, 29 - i, 18 + i, 48 + (i as i32 >> 1) * 64, true);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
hd(t, 32 + 8 * i + j, 39 + 8 * i - j, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n >= 3 {
|
||||
for i in 0..4 {
|
||||
hd(t, i, 7 - i, false, r);
|
||||
}
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..2 {
|
||||
bf(t, 13 - i, 10 + i, 32, true);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..2 {
|
||||
for j in 0..4 {
|
||||
hd(t, 16 + i * 8 + j, 23 + i * 8 - j, i == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..8 {
|
||||
bf(t, 59 - i, 36 + i, if i < 4 { 48 } else { 112 }, true);
|
||||
}
|
||||
}
|
||||
if n >= 4 {
|
||||
for i in 0..8 {
|
||||
hd(t, i, 15 - i, false, r);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..4 {
|
||||
bf(t, 27 - i, 20 + i, 32, true);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..8 {
|
||||
hd(t, 32 + i, 47 - i, false, r);
|
||||
hd(t, 48 + i, 63 - i, true, r);
|
||||
}
|
||||
}
|
||||
if n >= 5 {
|
||||
for i in 0..16 {
|
||||
hd(t, i, 31 - i, false, r);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..8 {
|
||||
bf(t, 55 - i, 40 + i, 32, true);
|
||||
}
|
||||
}
|
||||
if n == 6 {
|
||||
for i in 0..32 {
|
||||
hd(t, i, 63 - i, false, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SINPI_1_9: i64 = 1321;
|
||||
const SINPI_2_9: i64 = 2482;
|
||||
const SINPI_3_9: i64 = 3344;
|
||||
const SINPI_4_9: i64 = 3803;
|
||||
|
||||
fn inverse_adst4(t: &mut [i32]) {
|
||||
let mut s = [0i64; 7];
|
||||
let mut x = [0i64; 4];
|
||||
let t0 = t[0] as i64;
|
||||
let t1 = t[1] as i64;
|
||||
let t2 = t[2] as i64;
|
||||
let t3 = t[3] as i64;
|
||||
s[0] = SINPI_1_9 * t0;
|
||||
s[1] = SINPI_2_9 * t0;
|
||||
s[2] = SINPI_3_9 * t1;
|
||||
s[3] = SINPI_4_9 * t2;
|
||||
s[4] = SINPI_1_9 * t2;
|
||||
s[5] = SINPI_2_9 * t3;
|
||||
s[6] = SINPI_4_9 * t3;
|
||||
let a7 = t0 - t2;
|
||||
let b7 = a7 + t3;
|
||||
s[0] += s[3];
|
||||
s[1] -= s[4];
|
||||
s[3] = s[2];
|
||||
s[2] = SINPI_3_9 * b7;
|
||||
s[0] += s[5];
|
||||
s[1] -= s[6];
|
||||
x[0] = s[0] + s[3];
|
||||
x[1] = s[1] + s[3];
|
||||
x[2] = s[2];
|
||||
x[3] = s[0] + s[1];
|
||||
x[3] -= s[3];
|
||||
for i in 0..4 {
|
||||
t[i] = round2_64(x[i], 12);
|
||||
}
|
||||
}
|
||||
|
||||
fn adst_in_permute(t: &mut [i32], n: u32) {
|
||||
let n0 = 1usize << n;
|
||||
let mut copy = [0i32; 16];
|
||||
copy[..n0].copy_from_slice(&t[..n0]);
|
||||
for i in 0..n0 {
|
||||
let idx = if i & 1 == 1 { i - 1 } else { n0 - i - 1 };
|
||||
t[i] = copy[idx];
|
||||
}
|
||||
}
|
||||
|
||||
fn adst_out_permute(t: &mut [i32], n: u32) {
|
||||
let n0 = 1usize << n;
|
||||
let mut copy = [0i32; 16];
|
||||
copy[..n0].copy_from_slice(&t[..n0]);
|
||||
for i in 0..n0 {
|
||||
let a = (i >> 3) & 1;
|
||||
let b = ((i >> 2) & 1) ^ ((i >> 3) & 1);
|
||||
let c = ((i >> 1) & 1) ^ ((i >> 2) & 1);
|
||||
let d = (i & 1) ^ ((i >> 1) & 1);
|
||||
let idx = ((d << 3) | (c << 2) | (b << 1) | a) >> (4 - n);
|
||||
t[i] = if i & 1 == 1 { -copy[idx] } else { copy[idx] };
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_adst8(t: &mut [i32], r: u32) {
|
||||
adst_in_permute(t, 3);
|
||||
for i in 0..4 {
|
||||
bf(t, 2 * i, 2 * i + 1, 60 - 16 * i as i32, true);
|
||||
}
|
||||
for i in 0..4 {
|
||||
hd(t, i, 4 + i, false, r);
|
||||
}
|
||||
for i in 0..2 {
|
||||
bf(t, 4 + 3 * i, 5 + i, 48 - 32 * i as i32, true);
|
||||
}
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
hd(t, 4 * j + i, 2 + 4 * j + i, false, r);
|
||||
}
|
||||
}
|
||||
for i in 0..2 {
|
||||
bf(t, 2 + 4 * i, 3 + 4 * i, 32, true);
|
||||
}
|
||||
adst_out_permute(t, 3);
|
||||
}
|
||||
|
||||
fn inverse_adst16(t: &mut [i32], r: u32) {
|
||||
adst_in_permute(t, 4);
|
||||
for i in 0..8 {
|
||||
bf(t, 2 * i, 2 * i + 1, 62 - 8 * i as i32, true);
|
||||
}
|
||||
for i in 0..8 {
|
||||
hd(t, i, 8 + i, false, r);
|
||||
}
|
||||
for i in 0..2 {
|
||||
bf(t, 8 + 2 * i, 9 + 2 * i, 56 - 32 * i as i32, true);
|
||||
bf(t, 13 + 2 * i, 12 + 2 * i, 8 + 32 * i as i32, true);
|
||||
}
|
||||
for i in 0..4 {
|
||||
for j in 0..2 {
|
||||
hd(t, 8 * j + i, 4 + 8 * j + i, false, r);
|
||||
}
|
||||
}
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
bf(t, 4 + 8 * j + 3 * i, 5 + 8 * j + i, 48 - 32 * i as i32, true);
|
||||
}
|
||||
}
|
||||
for i in 0..2 {
|
||||
for j in 0..4 {
|
||||
hd(t, 4 * j + i, 2 + 4 * j + i, false, r);
|
||||
}
|
||||
}
|
||||
for i in 0..4 {
|
||||
bf(t, 2 + 4 * i, 3 + 4 * i, 32, true);
|
||||
}
|
||||
adst_out_permute(t, 4);
|
||||
}
|
||||
|
||||
fn inverse_adst(t: &mut [i32], n: u32, r: u32) {
|
||||
match n {
|
||||
2 => inverse_adst4(t),
|
||||
3 => inverse_adst8(t, r),
|
||||
_ => inverse_adst16(t, r),
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_identity(t: &mut [i32], n: u32) {
|
||||
match n {
|
||||
2 => {
|
||||
for v in t[..4].iter_mut() {
|
||||
*v = round2_64(*v as i64 * 5793, 12);
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for v in t[..8].iter_mut() {
|
||||
*v *= 2;
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
for v in t[..16].iter_mut() {
|
||||
*v = round2_64(*v as i64 * 11586, 12);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for v in t[..32].iter_mut() {
|
||||
*v *= 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_wht(t: &mut [i32], shift: u32) {
|
||||
let mut a = t[0] >> shift;
|
||||
let mut c = t[1] >> shift;
|
||||
let mut d = t[2] >> shift;
|
||||
let mut b = t[3] >> shift;
|
||||
a += c;
|
||||
d -= b;
|
||||
let e = (a - d) >> 1;
|
||||
b = e - b;
|
||||
c = e - c;
|
||||
a -= b;
|
||||
d += c;
|
||||
t[0] = a;
|
||||
t[1] = b;
|
||||
t[2] = c;
|
||||
t[3] = d;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Kind1d {
|
||||
Dct,
|
||||
Adst,
|
||||
Identity,
|
||||
}
|
||||
|
||||
fn row_kind(tx_type: usize) -> Kind1d {
|
||||
match tx_type {
|
||||
DCT_DCT | ADST_DCT | FLIPADST_DCT | H_DCT => Kind1d::Dct,
|
||||
DCT_ADST | ADST_ADST | DCT_FLIPADST | FLIPADST_FLIPADST | ADST_FLIPADST | FLIPADST_ADST | H_ADST | H_FLIPADST => Kind1d::Adst,
|
||||
_ => Kind1d::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn col_kind(tx_type: usize) -> Kind1d {
|
||||
match tx_type {
|
||||
DCT_DCT | DCT_ADST | DCT_FLIPADST | V_DCT => Kind1d::Dct,
|
||||
ADST_DCT | ADST_ADST | FLIPADST_DCT | FLIPADST_FLIPADST | ADST_FLIPADST | FLIPADST_ADST | V_ADST | V_FLIPADST => Kind1d::Adst,
|
||||
_ => Kind1d::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
/// 2D inverse transform (7.13.3). `dequant` is 64x64 (row stride 64, only the top-left 32x32
|
||||
/// may be non-zero); the residual is written to `residual` with row stride `w`.
|
||||
pub(crate) fn inverse_transform_2d(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, lossless: bool, bit_depth: u32) {
|
||||
let log2w = TX_WIDTH_LOG2[tx_sz] as u32;
|
||||
let log2h = TX_HEIGHT_LOG2[tx_sz] as u32;
|
||||
let w = 1usize << log2w;
|
||||
let h = 1usize << log2h;
|
||||
let row_shift = if lossless { 0 } else { TRANSFORM_ROW_SHIFT[tx_sz] as u32 };
|
||||
let col_shift = if lossless { 0 } else { 4 };
|
||||
let row_clamp = bit_depth + 8;
|
||||
let col_clamp = (bit_depth + 6).max(16);
|
||||
let rk = row_kind(tx_type);
|
||||
let ck = col_kind(tx_type);
|
||||
let mut t = [0i32; 64];
|
||||
let rect2 = log2w.abs_diff(log2h) == 1;
|
||||
for i in 0..h {
|
||||
if i >= 32 {
|
||||
// rows beyond 32 have no coefficients but still go through the transform
|
||||
t[..w].iter_mut().for_each(|v| *v = 0);
|
||||
} else {
|
||||
for j in 0..w {
|
||||
t[j] = if j < 32 { dequant[i * 64 + j] } else { 0 };
|
||||
}
|
||||
}
|
||||
if rect2 {
|
||||
for v in t[..w].iter_mut() {
|
||||
*v = round2_64(*v as i64 * 2896, 12);
|
||||
}
|
||||
}
|
||||
if lossless {
|
||||
inverse_wht(&mut t, 2);
|
||||
} else {
|
||||
match rk {
|
||||
Kind1d::Dct => inverse_dct(&mut t, log2w, row_clamp),
|
||||
Kind1d::Adst => inverse_adst(&mut t, log2w, row_clamp),
|
||||
Kind1d::Identity => inverse_identity(&mut t, log2w),
|
||||
}
|
||||
}
|
||||
let lo = -(1i32 << (col_clamp - 1));
|
||||
let hi = (1i32 << (col_clamp - 1)) - 1;
|
||||
for j in 0..w {
|
||||
residual[i * w + j] = round2(t[j], row_shift).clamp(lo, hi);
|
||||
}
|
||||
}
|
||||
for j in 0..w {
|
||||
for i in 0..h {
|
||||
t[i] = residual[i * w + j];
|
||||
}
|
||||
if lossless {
|
||||
inverse_wht(&mut t, 0);
|
||||
} else {
|
||||
match ck {
|
||||
Kind1d::Dct => inverse_dct(&mut t, log2h, col_clamp),
|
||||
Kind1d::Adst => inverse_adst(&mut t, log2h, col_clamp),
|
||||
Kind1d::Identity => inverse_identity(&mut t, log2h),
|
||||
}
|
||||
}
|
||||
for i in 0..h {
|
||||
residual[i * w + j] = round2(t[i], col_shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The DCT step list must implement an orthogonal transform: a DC-only input gives a flat
|
||||
/// output equal to DC * cos(pi/4) (in the 2^12 fixed-point scale).
|
||||
#[test]
|
||||
fn dct_dc_is_flat() {
|
||||
for n in 2..=6 {
|
||||
let len = 1 << n;
|
||||
let mut t = [0i32; 64];
|
||||
t[0] = 4096;
|
||||
inverse_dct(&mut t, n, 24);
|
||||
for v in &t[..len] {
|
||||
assert_eq!(*v, 2896, "n={n}: {:?}", &t[..len]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare each inverse DCT basis vector with the real-valued DCT-III.
|
||||
#[test]
|
||||
fn dct_matches_float() {
|
||||
for n in 2..=6u32 {
|
||||
let len = 1usize << n;
|
||||
for k in 0..len {
|
||||
let mut t = [0i32; 64];
|
||||
t[k] = 1 << 12;
|
||||
inverse_dct(&mut t, n, 24);
|
||||
for x in 0..len {
|
||||
let c = if k == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 };
|
||||
let want = 4096.0 * c * ((2 * x + 1) as f64 * k as f64 * std::f64::consts::PI / (2 * len) as f64).cos();
|
||||
assert!((t[x] as f64 - want).abs() < 4.0 + 0.5 * n as f64, "n={n} k={k} x={x}: {} vs {want}", t[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adst_is_close_to_sine_transform() {
|
||||
for n in 3..=4u32 {
|
||||
let len = 1usize << n;
|
||||
for k in 0..len {
|
||||
let mut t = [0i32; 64];
|
||||
t[k] = 1 << 12;
|
||||
inverse_adst(&mut t, n, 24);
|
||||
let energy: f64 = t[..len].iter().map(|&v| (v as f64 / 4096.0).powi(2)).sum();
|
||||
// like the DCT, the AV1 ADST is scaled by sqrt(N / 2)
|
||||
assert!((energy - len as f64 / 2.0).abs() < 0.02, "n={n} k={k} energy {energy}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
//! Shared helpers for the AV1 oracle tests. ffmpeg (libsvtav1 to encode fixtures, libdav1d to
|
||||
//! decode the reference) is used only as an external process.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use filmcraft_av1::{Decoder, Picture};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn ffmpeg() -> Option<PathBuf> {
|
||||
filmcraft_testkit::ffmpeg_or_skip("av1 oracle")
|
||||
}
|
||||
|
||||
pub fn fixture_dir() -> PathBuf {
|
||||
filmcraft_testkit::fixtures_dir("av1")
|
||||
}
|
||||
|
||||
pub fn run(ff: &Path, args: &[&str]) {
|
||||
let out = Command::new(ff).args(["-hide_banner", "-loglevel", "error", "-y"]).args(args).output().expect("run ffmpeg");
|
||||
assert!(out.status.success(), "ffmpeg {:?} failed: {}", args, String::from_utf8_lossy(&out.stderr));
|
||||
}
|
||||
|
||||
/// An AV1 fixture: a lavfi source encoded by ffmpeg's libsvtav1 into IVF.
|
||||
pub struct Spec {
|
||||
pub name: &'static str,
|
||||
pub lavfi: String,
|
||||
pub frames: u32,
|
||||
pub pix_fmt: &'static str,
|
||||
/// Extra encoder arguments.
|
||||
pub enc: Vec<String>,
|
||||
}
|
||||
|
||||
impl Spec {
|
||||
pub fn new(name: &'static str, lavfi: impl Into<String>, frames: u32, pix_fmt: &'static str, enc: &[&str]) -> Spec {
|
||||
Spec { name, lavfi: lavfi.into(), frames, pix_fmt, enc: enc.iter().map(|s| s.to_string()).collect() }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make(ff: &Path, spec: &Spec) -> PathBuf {
|
||||
let path = fixture_dir().join(format!("{}.ivf", spec.name));
|
||||
if !path.exists() {
|
||||
let tmp = filmcraft_testkit::temp_path(&path);
|
||||
let frames = spec.frames.to_string();
|
||||
let t = tmp.to_str().unwrap().to_string();
|
||||
let mut args: Vec<&str> = vec!["-f", "lavfi", "-i", spec.lavfi.as_str(), "-frames:v", frames.as_str(), "-pix_fmt", spec.pix_fmt, "-c:v", "libsvtav1"];
|
||||
args.extend(spec.enc.iter().map(String::as_str));
|
||||
args.extend(["-f", "ivf", t.as_str()]);
|
||||
run(ff, &args);
|
||||
std::fs::rename(&tmp, &path).unwrap();
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/// Frames of an IVF file.
|
||||
pub fn ivf_frames(path: &Path) -> Vec<Vec<u8>> {
|
||||
let d = std::fs::read(path).unwrap();
|
||||
assert_eq!(&d[0..4], b"DKIF");
|
||||
let hdr = u16::from_le_bytes([d[6], d[7]]) as usize;
|
||||
let mut pos = hdr;
|
||||
let mut out = Vec::new();
|
||||
while pos + 12 <= d.len() {
|
||||
let n = u32::from_le_bytes([d[pos], d[pos + 1], d[pos + 2], d[pos + 3]]) as usize;
|
||||
pos += 12;
|
||||
out.push(d[pos..pos + n].to_vec());
|
||||
pos += n;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Reference decode with ffmpeg's libdav1d (8-bit formats widened to u16).
|
||||
pub fn reference(ff: &Path, path: &Path, pix_fmt: &str) -> Vec<u16> {
|
||||
let out = Command::new(ff)
|
||||
.args(["-hide_banner", "-loglevel", "error", "-c:v", "libdav1d", "-i"])
|
||||
.arg(path)
|
||||
.args(["-f", "rawvideo", "-pix_fmt", pix_fmt, "-"])
|
||||
.output()
|
||||
.expect("run ffmpeg");
|
||||
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
if pix_fmt.ends_with("le") {
|
||||
out.stdout.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect()
|
||||
} else {
|
||||
out.stdout.iter().map(|&b| b as u16).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode every frame of an IVF file with our decoder.
|
||||
pub fn decode_all(path: &Path) -> Result<Vec<Picture>, String> {
|
||||
let mut dec = Decoder::new();
|
||||
let mut pics = Vec::new();
|
||||
for (i, f) in ivf_frames(path).iter().enumerate() {
|
||||
let p = dec.decode(f).map_err(|e| format!("frame {i}: {e}"))?;
|
||||
pics.extend(p);
|
||||
}
|
||||
Ok(pics)
|
||||
}
|
||||
|
||||
pub fn picture_samples(p: &Picture) -> usize {
|
||||
let mut n = (p.width * p.height) as usize;
|
||||
if !p.mono_chrome {
|
||||
n += 2 * (p.plane_width(1) * p.plane_height(1)) as usize;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Compare a decoded picture with the raw reference; returns the first mismatch as
|
||||
/// (plane, x, y, ours, reference) and the mismatch count.
|
||||
pub fn compare(p: &Picture, raw: &[u16]) -> (Option<(usize, u32, u32, u16, u16)>, usize) {
|
||||
let mut first = None;
|
||||
let mut count = 0;
|
||||
let mut off = 0;
|
||||
for plane in 0..if p.mono_chrome { 1 } else { 3 } {
|
||||
let w = p.plane_width(plane);
|
||||
let h = p.plane_height(plane);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let a = p.planes[plane][(y * w + x) as usize];
|
||||
let b = raw[off + (y * w + x) as usize];
|
||||
if a != b {
|
||||
count += 1;
|
||||
if first.is_none() {
|
||||
first = Some((plane, x, y, a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
off += (w * h) as usize;
|
||||
}
|
||||
(first, count)
|
||||
}
|
||||
|
||||
/// Decode a fixture and compare every frame with libdav1d; panics with details on mismatch.
|
||||
pub fn check_bit_exact(ff: &Path, spec: &Spec) {
|
||||
let path = make(ff, spec);
|
||||
let pics = decode_all(&path).unwrap_or_else(|e| panic!("{}: {e}", spec.name));
|
||||
let raw = reference(ff, &path, spec.pix_fmt);
|
||||
assert!(!pics.is_empty(), "{}: no frames", spec.name);
|
||||
let per = picture_samples(&pics[0]);
|
||||
assert_eq!(raw.len(), per * pics.len(), "{}: frame count/size ({} pictures)", spec.name, pics.len());
|
||||
for (i, p) in pics.iter().enumerate() {
|
||||
let (first, count) = compare(p, &raw[i * per..(i + 1) * per]);
|
||||
if let Some((plane, x, y, a, b)) = first {
|
||||
panic!("{}: frame {i} differs in {count} samples; first at plane {plane} ({x},{y}): ours {a}, reference {b}", spec.name);
|
||||
}
|
||||
}
|
||||
println!("{:<28} {} frames {}x{} bit-exact", spec.name, pics.len(), pics[0].width, pics[0].height);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
//! Intra-frame bit-exactness against libdav1d (run as an external ffmpeg process).
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
const NO_FILTERS: &str = "enable-cdef=0:enable-restoration=0:enable-dlf=0:keyint=1";
|
||||
|
||||
fn specs_no_filters() -> Vec<Spec> {
|
||||
let p = |preset: &'static str, crf: &'static str| -> Vec<&'static str> { vec!["-preset", preset, "-crf", crf, "-svtav1-params", NO_FILTERS] };
|
||||
vec![
|
||||
Spec::new("intra_nofilt_testsrc_128", "testsrc2=s=128x128:r=25", 1, "yuv420p", &p("8", "30")),
|
||||
Spec::new("intra_nofilt_mandel_352x288_p4", "mandelbrot=s=352x288:r=25", 2, "yuv420p", &p("4", "20")),
|
||||
Spec::new("intra_nofilt_testsrc_odd_p2", "testsrc2=s=203x117:r=25", 2, "yuv420p", &p("2", "35")),
|
||||
Spec::new("intra_nofilt_noise_p6", "testsrc2=s=320x240:r=25,noise=alls=30:allf=t", 2, "yuv420p", &p("6", "10")),
|
||||
Spec::new("intra_nofilt_10bit_p4", "mandelbrot=s=256x192:r=25", 2, "yuv420p10le", &p("4", "25")),
|
||||
Spec::new("intra_nofilt_720p_p8", "testsrc2=s=1280x720:r=25", 1, "yuv420p", &p("8", "40")),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intra_no_filters() {
|
||||
let Some(ff) = ffmpeg() else { return };
|
||||
for s in specs_no_filters() {
|
||||
check_bit_exact(&ff, &s);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Extract the constants and constant tables of the AV1 Bitstream & Decoding Process
|
||||
Specification (v1.0.0 with Errata 1) from its Markdown source.
|
||||
|
||||
Usage:
|
||||
curl -L -o av1-spec.zip https://github.com/AOMediaCodec/av1-spec/archive/5e04f3f75e73a5898d7616c47c52f032144b8f80.zip
|
||||
unzip av1-spec.zip
|
||||
python3 extract_tables.py av1-spec-5e04f3f75e73a5898d7616c47c52f032144b8f80 > ../src/spec_tables.rs
|
||||
|
||||
Only the specification text is read:
|
||||
* named constants from the symbol table of section 3 and the value/name tables of section 6
|
||||
(e.g. `| 1 | BLOCK_4X8`);
|
||||
* every C table initialiser `Name[ dims ] = { ... }` in any section.
|
||||
Each table is emitted as a nested Rust array whose shape is checked against the declared
|
||||
dimensions; the element type is the narrowest of u8/i8/u16/i16/i32 that holds every value.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
root = sys.argv[1]
|
||||
files = sorted(f for f in os.listdir(root) if f.endswith(".md"))
|
||||
texts = {f: open(os.path.join(root, f), encoding="utf-8").read() for f in files}
|
||||
|
||||
consts = {}
|
||||
|
||||
|
||||
def ev(expr):
|
||||
e = expr.strip().replace("\\", "")
|
||||
e = re.sub(r"\b([A-Z][A-Z0-9_]*)\b", lambda m: str(consts[m.group(1)]) if m.group(1) in consts else m.group(1), e)
|
||||
e = e.replace("/", "//")
|
||||
return int(eval(e, {"__builtins__": {}}, {}))
|
||||
|
||||
|
||||
# --- constants: symbol table (section 3) ---
|
||||
pending = []
|
||||
for line in texts["03.symbols.md"].split("\n"):
|
||||
m = re.match(r"^\|\s*`([A-Z][A-Z0-9_]*)`\s*\|\s*([^|]+?)\s*\|", line)
|
||||
if m:
|
||||
pending.append((m.group(1), m.group(2)))
|
||||
# --- constants: value/name tables (section 6 semantics, section 8/9) ---
|
||||
# A row cell holding a NAME takes its value from the integer cell just before it, unless the
|
||||
# next cell is another NAME (multi-column mapping tables such as the CFL sign table).
|
||||
NAME = re.compile(r"^[A-Z][A-Z0-9_]*[A-Z0-9]$")
|
||||
for f in files:
|
||||
for line in texts[f].split("\n"):
|
||||
if not line.startswith("|"):
|
||||
continue
|
||||
cells = [c.strip().rstrip(".") for c in line.strip().strip("|").split("|")]
|
||||
for k in range(1, len(cells)):
|
||||
if NAME.match(cells[k]) and "_" in cells[k] and re.match(r"^-?\d+$", cells[k - 1]):
|
||||
if k + 1 < len(cells) and NAME.match(cells[k + 1]) and "_" in cells[k + 1]:
|
||||
continue
|
||||
pending.append((cells[k], cells[k - 1]))
|
||||
# `| NAME | value` rows
|
||||
if len(cells) >= 2 and NAME.match(cells[0]) and "_" in cells[0] and re.match(r"^-?\d+$", cells[1]):
|
||||
pending.append((cells[0], cells[1]))
|
||||
for _ in range(10):
|
||||
rest = []
|
||||
for name, val in pending:
|
||||
try:
|
||||
v = ev(val)
|
||||
except Exception:
|
||||
rest.append((name, val))
|
||||
continue
|
||||
if name in consts and consts[name] != v:
|
||||
# same name, different meaning in two tables: keep the first (reported)
|
||||
sys.stderr.write("constant %s: %d vs %d (kept first)\n" % (name, consts[name], v))
|
||||
continue
|
||||
consts[name] = v
|
||||
pending = rest
|
||||
for name, val in pending:
|
||||
sys.stderr.write("unresolved constant %s = %s\n" % (name, val))
|
||||
|
||||
|
||||
# --- tables ---
|
||||
def cells(text):
|
||||
"""Split one initialiser cell. A missing comma between two operands on separate lines
|
||||
(Split_Tx_Size in the spec text: `TX_32X32` / `TX_4X8`) yields two cells."""
|
||||
parts = re.split(r"(?<=[A-Za-z0-9_])\s*\n\s*(?=[A-Za-z0-9_])", text.strip())
|
||||
if len(parts) > 1:
|
||||
sys.stderr.write("note: missing comma in spec table between %s\n" % parts)
|
||||
return parts
|
||||
|
||||
|
||||
def parse_braces(s, i):
|
||||
"""Parse a brace initialiser starting at s[i] == '{'; returns (nested list, end index)."""
|
||||
assert s[i] == "{"
|
||||
i += 1
|
||||
items = []
|
||||
cur = ""
|
||||
while True:
|
||||
ch = s[i]
|
||||
if ch == "{":
|
||||
sub, i = parse_braces(s, i)
|
||||
items.append(sub)
|
||||
cur = ""
|
||||
continue
|
||||
if ch == "}":
|
||||
if cur.strip():
|
||||
items.extend(cells(cur))
|
||||
return items, i + 1
|
||||
if ch == ",":
|
||||
if cur.strip():
|
||||
items.extend(cells(cur))
|
||||
cur = ""
|
||||
else:
|
||||
cur += ch
|
||||
i += 1
|
||||
|
||||
|
||||
def shape(x):
|
||||
if isinstance(x, list):
|
||||
subs = [shape(e) for e in x]
|
||||
if any(s != subs[0] for s in subs):
|
||||
return None
|
||||
return [len(x)] + (subs[0] if subs else [])
|
||||
return []
|
||||
|
||||
|
||||
tables = {}
|
||||
for f in files:
|
||||
t = texts[f]
|
||||
for block in re.findall(r"~~~~~?\s*c?\n(.*?)~~~~~?", t, re.S):
|
||||
b = re.sub(r"//[^\n]*", "", block)
|
||||
b = re.sub(r"/\*.*?\*/", "", b, flags=re.S)
|
||||
for m in re.finditer(r"(?m)^\s*([A-Z][A-Za-z0-9_]*)\s*((?:\[[^\]=]*\]\s*)+)=\s*", b):
|
||||
name = m.group(1)
|
||||
try:
|
||||
dims = [ev(d) for d in re.findall(r"\[([^\]]*)\]", m.group(2))]
|
||||
except Exception:
|
||||
continue # an assignment in pseudo code, not a table
|
||||
j = b.find("{", m.end())
|
||||
if j < 0 or b[m.end():j].strip():
|
||||
continue
|
||||
val, _ = parse_braces(b, j)
|
||||
sh = shape(val)
|
||||
if sh != dims:
|
||||
sys.stderr.write("table %s: shape %s != declared %s (skipped)\n" % (name, sh, dims))
|
||||
continue
|
||||
flat = []
|
||||
|
||||
def walk(x):
|
||||
if isinstance(x, list):
|
||||
out = []
|
||||
for e in x:
|
||||
out.append(walk(e))
|
||||
return out
|
||||
v = ev(x)
|
||||
flat.append(v)
|
||||
return v
|
||||
|
||||
nested = walk(val)
|
||||
if name in tables and tables[name][1] != nested:
|
||||
sys.stderr.write("table %s defined twice with different values\n" % name)
|
||||
tables[name] = (dims, nested, flat)
|
||||
|
||||
|
||||
def rust_type(flat):
|
||||
lo, hi = min(flat), max(flat)
|
||||
for t, a, b in (("u8", 0, 255), ("i8", -128, 127), ("u16", 0, 65535), ("i16", -32768, 32767)):
|
||||
if lo >= a and hi <= b:
|
||||
return t
|
||||
return "i32"
|
||||
|
||||
|
||||
def fmt(x, depth):
|
||||
if isinstance(x, list) and x and isinstance(x[0], list):
|
||||
inner = ",\n".join(" " * (depth + 1) + fmt(e, depth + 1) for e in x)
|
||||
return "[\n" + inner + ",\n" + " " * depth + "]"
|
||||
return "[" + ", ".join(str(v) for v in x) + "]"
|
||||
|
||||
|
||||
out = [
|
||||
"// Generated by tools/extract_tables.py from the AV1 Bitstream & Decoding Process Specification",
|
||||
"// v1.0.0 with Errata 1 (AOMediaCodec/av1-spec 5e04f3f). Do not edit by hand.",
|
||||
"#![allow(dead_code, clippy::unreadable_literal, clippy::excessive_precision)]",
|
||||
"",
|
||||
]
|
||||
for name in sorted(consts):
|
||||
v = consts[name]
|
||||
out.append("pub const %s: %s = %d;" % (name, "i32" if v < 0 else "usize", v))
|
||||
out.append("")
|
||||
for name in sorted(tables):
|
||||
dims, nested, flat = tables[name]
|
||||
# scan tables share one element type so they can be returned as &[u16]
|
||||
t = "u16" if "_Scan_" in name else rust_type(flat)
|
||||
rname = name.upper()
|
||||
if rname in consts:
|
||||
rname += "_TABLE" # e.g. Max_Tx_Depth (table) vs MAX_TX_DEPTH (constant)
|
||||
ty = t
|
||||
for d in reversed(dims):
|
||||
ty = "[%s; %d]" % (ty, d)
|
||||
out.append("pub static %s: %s = %s;" % (rname, ty, fmt(nested, 0)))
|
||||
out.append("")
|
||||
print("\n".join(out))
|
||||
sys.stderr.write("%d constants, %d tables\n" % (len(consts), len(tables)))
|
||||
|
|
@ -27,7 +27,7 @@ Design principles:
|
|||
L2 edit · codecs · interchange · captions
|
||||
L1 frame · media · project · audio-dsp · text
|
||||
L0 foundation: time · geom · color · bitstream · testkit (dev-dependency only)
|
||||
codecs/containers: isobmff · matroska · h264 · h264enc · hevc · vp9 · prores · dnx · aac · opus
|
||||
codecs/containers: isobmff · matroska · h264 · h264enc · hevc · vp9 · av1 · prores · dnx · aac · opus
|
||||
```
|
||||
|
||||
Crates are named `filmcraft-<dir>` (`crates/time` is `filmcraft-time`). The apps are `filmcraft`
|
||||
|
|
@ -45,6 +45,7 @@ and `filmcraft-cli`.
|
|||
| `hevc` | L0 | H.265 Main/Main 10 decoder |
|
||||
| `prores` | L0 | ProRes decoder and encoder |
|
||||
| `dnx` | L0 | DNxHD / DNxHR (SMPTE ST 2019-1 VC-3) decoder and DNxHR encoder |
|
||||
| `av1` | L0 | AV1 decoder (in progress; see its README for the stage table) |
|
||||
| `aac` | L0 | AAC-LC decoder and encoder |
|
||||
| `testkit` | L0 | test-only helpers, used only as a dev-dependency: ffmpeg/ffprobe discovery, fixture dirs, golden images ([testing.md](testing.md)) |
|
||||
| `frame` | L1 | `VideoFrame` (planar YUV / RGBA8 / linear RGBA f32, colour metadata), `AudioBuffer` |
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const LAYERS: &[(&str, u8)] = &[
|
|||
("prores", 0),
|
||||
("mjpeg", 0),
|
||||
("dnx", 0),
|
||||
("av1", 0),
|
||||
("aac", 0),
|
||||
("opus", 0),
|
||||
("frame", 1),
|
||||
|
|
|
|||
Loading…
Reference in New Issue