mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 09:37:11 +00:00
craft-video: VP8 key-frame encoder (vp8-encode feature) (#969)
Moves effectcraft's effectcraft-vp8enc here: the lossy picture of a WebP image or animation frame, written from RFC 6386. Rate-distortion choice between the 16x16 and B_PRED intra modes, per-frame token probability updates, loop filter level from the quantizer. `vp8` now turns on `vp8-decode` and `vp8-encode`, as `vp9` does; the decoder's tests and examples require `vp8-decode`. The tests decode the encoder's frames with this crate's decoder: with the loop filter off it reproduces the encoder's reconstruction exactly.
This commit is contained in:
+11
-5
@@ -21,8 +21,10 @@ dnx = []
|
||||
apv = ["apv-decode", "apv-encode"]
|
||||
apv-decode = ["craft-media/bitstream"]
|
||||
apv-encode = ["craft-media/bitstream"]
|
||||
# VP8 (RFC 6386): decoder.
|
||||
vp8 = []
|
||||
# VP8 (RFC 6386): decoder and encoder; `vp8-decode` or `vp8-encode` for one of them.
|
||||
vp8 = ["vp8-decode", "vp8-encode"]
|
||||
vp8-decode = []
|
||||
vp8-encode = []
|
||||
# MPEG-2 video (ITU-T H.262) and MPEG-1 video (ISO/IEC 11172-2): decoder.
|
||||
mpeg2 = []
|
||||
# Apple ProRes (SMPTE RDD 36): decoder and encoder.
|
||||
@@ -64,15 +66,15 @@ required-features = ["apv"]
|
||||
|
||||
[[test]]
|
||||
name = "vp8"
|
||||
required-features = ["vp8"]
|
||||
required-features = ["vp8-decode"]
|
||||
|
||||
[[example]]
|
||||
name = "vp8_fuzz"
|
||||
required-features = ["vp8"]
|
||||
required-features = ["vp8-decode"]
|
||||
|
||||
[[example]]
|
||||
name = "vp8_vp8dec"
|
||||
required-features = ["vp8"]
|
||||
required-features = ["vp8-decode"]
|
||||
|
||||
[[test]]
|
||||
name = "av1_decode"
|
||||
@@ -98,6 +100,10 @@ required-features = ["hevc-decode"]
|
||||
name = "hevc_hevcdec"
|
||||
required-features = ["hevc-decode"]
|
||||
|
||||
[[test]]
|
||||
name = "vp8_encode"
|
||||
required-features = ["vp8"]
|
||||
|
||||
[[test]]
|
||||
name = "vp9_decode"
|
||||
required-features = ["vp9-decode"]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! |---|---|---|
|
||||
//! | `dnx` | [`dnx`] | Avid DNxHD / DNxHR (SMPTE ST 2019-1): decoder and encoder |
|
||||
//! | `apv` | [`apv`] | APV (RFC 9924): decoder (`apv-decode`) and encoder (`apv-encode`) |
|
||||
//! | `vp8` | [`vp8`] | VP8 (RFC 6386): decoder |
|
||||
//! | `vp8` | [`vp8`] | VP8 (RFC 6386): decoder (`vp8-decode`) and key-frame encoder (`vp8-encode`) |
|
||||
//! | `mpeg2` | [`mpeg2`] | MPEG-2 video (ITU-T H.262) and MPEG-1 video: decoder |
|
||||
//! | `prores` | [`prores`] | Apple ProRes (SMPTE RDD 36): decoder and encoder |
|
||||
//! | `h264` | [`h264`] | ITU-T H.264 / AVC: decoder (`h264-decode`) and encoder (`h264-encode`) |
|
||||
@@ -31,7 +31,7 @@ pub mod apv;
|
||||
#[cfg(feature = "dnx")]
|
||||
pub mod dnx;
|
||||
|
||||
#[cfg(feature = "vp8")]
|
||||
#[cfg(any(feature = "vp8-decode", feature = "vp8-encode"))]
|
||||
pub mod vp8;
|
||||
|
||||
#[cfg(feature = "mpeg2")]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# VP8 encoder (`vp8-encode`)
|
||||
|
||||
A VP8 key-frame encoder: the lossy picture inside a WebP image or animation frame (a `VP8 `
|
||||
chunk). WebP's lossy frames are VP8 key frames, so that is all this encodes; there are no inter
|
||||
frames.
|
||||
|
||||
* **Prediction**: every macroblock is coded with the best of the four 16×16 luma modes (DC, V, H,
|
||||
TM, with the second-order Walsh–Hadamard "Y2" block of the subblocks' DC coefficients) or
|
||||
B_PRED (each 4×4 subblock with the best of its ten modes, predicted from the subblocks before
|
||||
it), and the best of the four 8×8 chroma modes.
|
||||
* **Choice**: by rate–distortion cost, squared error plus λ times the bits, the bits counted from
|
||||
the token and mode probabilities in the contexts a decoder uses.
|
||||
* **Quantization**: one quality setting (0–100) picks the frame's quantizer index; luma DC, Y2 and
|
||||
chroma factors follow from it as the format derives them. Macroblocks left with nothing to code
|
||||
are skipped (`mb_skip_coeff`).
|
||||
* **Probabilities**: the coefficient token probabilities are updated per frame where the frame's
|
||||
own token counts save more than the update costs.
|
||||
* **Loop filter**: the level is chosen from the quantizer (normal filter, sharpness 0). Intra
|
||||
prediction reads the unfiltered reconstruction, so the filter shapes only the decoded picture.
|
||||
* **Colour**: RGBA → BT.601 studio-range Y′CbCr 4:2:0, chroma averaged per 2×2 weighted by alpha
|
||||
so colour under full transparency does not tint visible edges.
|
||||
|
||||
The encoder reconstructs exactly what a decoder reconstructs before loop filtering (prediction
|
||||
with the frame-edge values of §12, the 16-bit inverse transforms of §14) and returns it with the
|
||||
frame; the tests require a decoder (ffmpeg, when installed) to reproduce it exactly, and the
|
||||
`image` crate's WebP decoder to read the frame back.
|
||||
|
||||
Written from **RFC 6386, *VP8 Data Format and Decoding Guide* (November 2011)**, sections 1–19 (the
|
||||
format description and Annex A's bitstream syntax). The constant tables (§11.5, §13.4, §13.5,
|
||||
§14.1) are generated from the RFC's text. Its Attachment One, the reference decoder source, was
|
||||
not used; neither was any other encoder or decoder's source. Where the RFC's text leaves a detail
|
||||
to that source — the coefficient scan order and the Y2 and chroma dequantization factors — the
|
||||
encoder uses the format's known values, checked by decoding its output with independent decoders.
|
||||
The forward transforms are derived from §14's inverse transforms (see `src/transform.rs`).
|
||||
@@ -0,0 +1,233 @@
|
||||
//! The boolean entropy encoder (RFC 6386 §7) and the cost of coding a bool.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Writes bools at 8-bit probabilities, as §7.3 describes: `bottom` and `range` are the coding
|
||||
/// interval, `bit_count` the shifts left before a byte of `bottom` is ready.
|
||||
pub(crate) struct BoolWriter {
|
||||
out: Vec<u8>,
|
||||
range: u32,
|
||||
bottom: u32,
|
||||
bit_count: i32,
|
||||
}
|
||||
|
||||
impl BoolWriter {
|
||||
/// An empty writer, its range full (§7.3).
|
||||
pub(crate) fn new() -> BoolWriter {
|
||||
BoolWriter { out: Vec::new(), range: 255, bottom: 0, bit_count: 24 }
|
||||
}
|
||||
|
||||
/// A bool whose probability of being false is `prob / 256`.
|
||||
pub(crate) fn put(&mut self, prob: u8, bit: bool) {
|
||||
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
|
||||
if bit {
|
||||
self.bottom = self.bottom.wrapping_add(split);
|
||||
self.range -= split;
|
||||
} else {
|
||||
self.range = split;
|
||||
}
|
||||
while self.range < 128 {
|
||||
self.range <<= 1;
|
||||
if self.bottom & (1 << 31) != 0 {
|
||||
self.carry();
|
||||
}
|
||||
self.bottom <<= 1;
|
||||
self.bit_count -= 1;
|
||||
if self.bit_count == 0 {
|
||||
self.out.push((self.bottom >> 24) as u8);
|
||||
self.bottom &= (1 << 24) - 1;
|
||||
self.bit_count = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An unsigned `bits`-wide literal, high bit first, each bit at probability 1/2.
|
||||
pub(crate) fn literal(&mut self, bits: u32, value: u32) {
|
||||
for b in (0..bits).rev() {
|
||||
self.put(128, (value >> b) & 1 == 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The bytes written, the interval's remainder appended (§7.3, flush).
|
||||
pub(crate) fn finish(mut self) -> Vec<u8> {
|
||||
let mut c = self.bit_count;
|
||||
let mut v = self.bottom;
|
||||
if c < 32 && v & (1 << (32 - c)) != 0 {
|
||||
self.carry();
|
||||
}
|
||||
v <<= c & 7;
|
||||
c >>= 3;
|
||||
while c > 0 {
|
||||
v <<= 8;
|
||||
c -= 1;
|
||||
}
|
||||
for _ in 0..4 {
|
||||
self.out.push((v >> 24) as u8);
|
||||
v <<= 8;
|
||||
}
|
||||
self.out
|
||||
}
|
||||
|
||||
/// Adds one to the bytes already written. The coded value is always less than one, so the
|
||||
/// carry never runs off the front.
|
||||
fn carry(&mut self) {
|
||||
for b in self.out.iter_mut().rev() {
|
||||
if *b == 255 {
|
||||
*b = 0;
|
||||
} else {
|
||||
*b += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bits a bool costs at `prob`: −log2 of its probability.
|
||||
pub(crate) fn cost(prob: u8, bit: bool) -> f32 {
|
||||
static TABLE: OnceLock<[f32; 256]> = OnceLock::new();
|
||||
let t = TABLE.get_or_init(|| {
|
||||
let mut t = [0f32; 256];
|
||||
for (p, c) in t.iter_mut().enumerate() {
|
||||
*c = -((p.max(1) as f32) / 256.0).log2();
|
||||
}
|
||||
t
|
||||
});
|
||||
let p = if bit { 256 - usize::from(prob) } else { usize::from(prob) };
|
||||
t[p.min(255)]
|
||||
}
|
||||
|
||||
/// Writes `leaf` of a tree in the RFC's array form (§8.1): even entries are pairs of branches, a
|
||||
/// positive entry the index of a deeper pair, a non-positive one the leaf `-entry`. `probs` are
|
||||
/// indexed by half the pair's index. Returns false when the tree has no such leaf.
|
||||
pub(crate) fn put_tree(w: &mut BoolWriter, tree: &[i8], probs: &[u8], leaf: u8) -> bool {
|
||||
let mut path = Vec::with_capacity(12);
|
||||
if !find(tree, 0, leaf, &mut path, 0) {
|
||||
return false;
|
||||
}
|
||||
for (node, bit) in path {
|
||||
w.put(probs.get(node / 2).copied().unwrap_or(128), bit);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Bits [`put_tree`] would spend.
|
||||
pub(crate) fn tree_cost(tree: &[i8], probs: &[u8], leaf: u8) -> f32 {
|
||||
let mut path = Vec::with_capacity(12);
|
||||
if !find(tree, 0, leaf, &mut path, 0) {
|
||||
return 0.0;
|
||||
}
|
||||
path.iter().map(|&(node, bit)| cost(probs.get(node / 2).copied().unwrap_or(128), bit)).sum()
|
||||
}
|
||||
|
||||
/// Find the path from `node` to `leaf` in a token tree, pushing each (node, bit) on the way; false if it isn't there.
|
||||
fn find(tree: &[i8], node: usize, leaf: u8, path: &mut Vec<(usize, bool)>, depth: usize) -> bool {
|
||||
if depth > 16 {
|
||||
return false;
|
||||
}
|
||||
for bit in [false, true] {
|
||||
let Some(&entry) = tree.get(node + usize::from(bit)) else { return false };
|
||||
path.push((node, bit));
|
||||
let hit = if entry > 0 { find(tree, entry as usize, leaf, path, depth + 1) } else { (-i16::from(entry)) as u8 == leaf };
|
||||
if hit {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The decoder of §7.3, to read back what the writer wrote.
|
||||
pub(crate) struct BoolReader<'a> {
|
||||
data: &'a [u8],
|
||||
pos: usize,
|
||||
value: u32,
|
||||
range: u32,
|
||||
bit_count: u32,
|
||||
}
|
||||
|
||||
impl<'a> BoolReader<'a> {
|
||||
/// A reader over `data`, primed with its first two bytes.
|
||||
pub(crate) fn new(data: &'a [u8]) -> Self {
|
||||
let byte = |i: usize| u32::from(data.get(i).copied().unwrap_or(0));
|
||||
BoolReader { data, pos: 2, value: (byte(0) << 8) | byte(1), range: 255, bit_count: 0 }
|
||||
}
|
||||
/// Read one bool coded with probability `prob` (of a 0).
|
||||
pub(crate) fn get(&mut self, prob: u8) -> bool {
|
||||
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
|
||||
let big = split << 8;
|
||||
let bit = if self.value >= big {
|
||||
self.range -= split;
|
||||
self.value -= big;
|
||||
true
|
||||
} else {
|
||||
self.range = split;
|
||||
false
|
||||
};
|
||||
while self.range < 128 {
|
||||
self.value <<= 1;
|
||||
self.range <<= 1;
|
||||
self.bit_count += 1;
|
||||
if self.bit_count == 8 {
|
||||
self.bit_count = 0;
|
||||
self.value |= u32::from(self.data.get(self.pos).copied().unwrap_or(0));
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
bit
|
||||
}
|
||||
}
|
||||
|
||||
/// Bools written at assorted probabilities read back unchanged.
|
||||
#[test]
|
||||
fn bools_read_back() {
|
||||
let mut w = BoolWriter::new();
|
||||
let mut want = Vec::new();
|
||||
let mut x = 12345u32;
|
||||
for i in 0..20000 {
|
||||
x = x.wrapping_mul(1103515245).wrapping_add(12345);
|
||||
let prob = ((x >> 16) % 255 + 1) as u8;
|
||||
let bit = (x >> 8) % 256 >= u32::from(prob) || i % 97 == 0;
|
||||
w.put(prob, bit);
|
||||
want.push((prob, bit));
|
||||
}
|
||||
let data = w.finish();
|
||||
let mut r = BoolReader::new(&data);
|
||||
for (i, (prob, bit)) in want.into_iter().enumerate() {
|
||||
assert_eq!(r.get(prob), bit, "bool {i}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Each leaf of a tree reads back as the leaf that was written.
|
||||
#[test]
|
||||
fn trees_write_their_leaves() {
|
||||
// kf_ymode_tree (§11.2): B_PRED = 0, DC = 100, V = 101, H = 110, TM = 111.
|
||||
const TREE: [i8; 8] = [-4, 2, 4, 6, 0, -1, -2, -3];
|
||||
for leaf in 0..5u8 {
|
||||
let mut w = BoolWriter::new();
|
||||
assert!(put_tree(&mut w, &TREE, &[145, 156, 163, 128], leaf));
|
||||
let data = w.finish();
|
||||
let mut r = BoolReader::new(&data);
|
||||
let read = if !r.get(145) {
|
||||
4
|
||||
} else if !r.get(156) {
|
||||
u8::from(r.get(163))
|
||||
} else {
|
||||
2 + u8::from(r.get(128))
|
||||
};
|
||||
assert_eq!(read, leaf);
|
||||
}
|
||||
assert!(!put_tree(&mut BoolWriter::new(), &TREE, &[128; 4], 9));
|
||||
}
|
||||
|
||||
/// A bool's cost is its information in bits: −log₂ of its probability.
|
||||
#[test]
|
||||
fn costs_are_information() {
|
||||
assert!((cost(128, false) - 1.0).abs() < 1e-6);
|
||||
assert!((cost(192, false) - 0.415).abs() < 1e-3);
|
||||
assert!((cost(192, true) - 2.0).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,787 @@
|
||||
//! Mode decision, quantization, reconstruction and the bitstream of a key frame (RFC 6386 §9–§14
|
||||
//! and Annex A).
|
||||
//!
|
||||
//! Three passes over the macroblocks: the first chooses each one's modes and quantized
|
||||
//! coefficients and reconstructs it as a decoder will (later macroblocks are predicted from that
|
||||
//! reconstruction); the second counts the coefficient tokens, from which the probability updates
|
||||
//! are chosen; the third writes the first partition (header and modes) and the token partition.
|
||||
|
||||
use super::bool::{BoolWriter, cost, put_tree, tree_cost};
|
||||
use super::predict::{B_DC_PRED, B_HE_PRED, B_PRED, B_TM_PRED, B_VE_PRED, DC_PRED, Edges, H_PRED, TM_PRED, V_PRED, predict_block, predict_sub};
|
||||
use super::tables::{
|
||||
AC_QLOOKUP, CAT_BASE, COEFF_BANDS, COEFF_UPDATE_PROBS, DC_QLOOKUP, DEFAULT_COEFF_PROBS, KF_BMODE_PROB, KF_UV_MODE_PROB, KF_YMODE_PROB, PCAT,
|
||||
};
|
||||
use super::transform::{fdct, fwht, idct, iwht};
|
||||
use super::{Vp8Error, Vp8Frame, Vp8Params, q_index};
|
||||
|
||||
/// One 8-bit plane.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Plane {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
/// A zeroed `width`×`height` plane.
|
||||
pub(crate) fn new(width: usize, height: usize) -> Plane {
|
||||
Plane { width, height, data: vec![0; width * height] }
|
||||
}
|
||||
/// The sample at (x, y); 0 outside the plane.
|
||||
pub fn get(&self, x: usize, y: usize) -> u8 {
|
||||
if x < self.width { self.data.get(y * self.width + x).copied().unwrap_or(0) } else { 0 }
|
||||
}
|
||||
/// Set the sample at (x, y); writes outside the plane are ignored.
|
||||
pub(crate) fn set(&mut self, x: usize, y: usize, v: u8) {
|
||||
if x < self.width
|
||||
&& let Some(p) = self.data.get_mut(y * self.width + x)
|
||||
{
|
||||
*p = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The scan order of coefficients: position in the scan → raster index in the 4×4 block.
|
||||
const ZIGZAG: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
|
||||
|
||||
/// The mode trees of §11.2 and §11.4 in the RFC's array form.
|
||||
const KF_YMODE_TREE: [i8; 8] = [-4, 2, 4, 6, 0, -1, -2, -3];
|
||||
const UV_MODE_TREE: [i8; 6] = [0, 2, -1, 4, -2, -3];
|
||||
const BMODE_TREE: [i8; 18] = [0, 2, -1, 4, -2, 6, 8, 12, -3, 10, -5, -6, -4, 14, -7, 16, -8, -9];
|
||||
|
||||
/// Coefficient block types, the outer index of the token probabilities (§13.3).
|
||||
const TYPE_Y_AFTER_Y2: usize = 0;
|
||||
const TYPE_Y2: usize = 1;
|
||||
const TYPE_UV: usize = 2;
|
||||
const TYPE_Y_WITH_DC: usize = 3;
|
||||
|
||||
type Probs = [[[[u8; 11]; 3]; 8]; 4];
|
||||
|
||||
/// Lagrange multiplier per squared AC step: the weight of a bit against squared error.
|
||||
const LAMBDA_PER_STEP2: f64 = 0.02;
|
||||
|
||||
/// Dequantization factors, `[dc, ac]` for luma, Y2 and chroma, from one quantizer index.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Quant {
|
||||
y1: [i32; 2],
|
||||
y2: [i32; 2],
|
||||
uv: [i32; 2],
|
||||
}
|
||||
|
||||
impl Quant {
|
||||
/// The dequantization factors for quantizer index `q` (0–127, §14.1).
|
||||
fn new(q: u8) -> Quant {
|
||||
let q = usize::from(q.min(127));
|
||||
let (dc, ac) = (DC_QLOOKUP[q], AC_QLOOKUP[q]);
|
||||
Quant { y1: [dc, ac], y2: [dc * 2, (ac * 155 / 100).max(8)], uv: [dc.min(132), ac] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A coefficient quantized to a level a decoder can hold: the token alphabet stops at 2048 and the
|
||||
/// dequantized value is a 16-bit integer.
|
||||
fn quantize(c: f64, dq: i32, dc: bool) -> i16 {
|
||||
let bias = if dc { 0.45 } else { 0.38 };
|
||||
let max = (32767 / dq.max(1)).min(2048);
|
||||
let level = ((c.abs() / f64::from(dq.max(1))) + bias).floor().min(f64::from(max)) as i16;
|
||||
if c < 0.0 { -level } else { level }
|
||||
}
|
||||
|
||||
/// A level times its dequantization factor, as a decoder computes it.
|
||||
fn dequant(level: i16, dq: i32) -> i16 {
|
||||
(i32::from(level) * dq) as i16
|
||||
}
|
||||
|
||||
/// Whether neighbouring blocks had non-zero coefficients, per plane: the first token's context.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct Nz {
|
||||
y: [bool; 4],
|
||||
u: [bool; 2],
|
||||
v: [bool; 2],
|
||||
y2: bool,
|
||||
}
|
||||
|
||||
/// What is decided for a macroblock. Levels are in raster order within each 4×4 block.
|
||||
#[derive(Clone)]
|
||||
struct Mb {
|
||||
ymode: u8,
|
||||
bmodes: [u8; 16],
|
||||
uvmode: u8,
|
||||
skip: bool,
|
||||
y2: [i16; 16],
|
||||
y: [[i16; 16]; 16],
|
||||
u: [[i16; 16]; 4],
|
||||
v: [[i16; 16]; 4],
|
||||
}
|
||||
|
||||
/// The subblock mode a whole-block luma mode stands for in later blocks' contexts (§11.3).
|
||||
fn implied_bmode(ymode: u8) -> u8 {
|
||||
match ymode {
|
||||
V_PRED => B_VE_PRED,
|
||||
H_PRED => B_HE_PRED,
|
||||
TM_PRED => B_TM_PRED,
|
||||
_ => B_DC_PRED,
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives the bools of coefficient tokens: to cost them, count them or write them.
|
||||
trait TokenSink {
|
||||
/// `node` is the token tree node (for counting), `None` for extra bits and signs.
|
||||
fn bit(&mut self, ty: usize, band: usize, ctx: usize, node: Option<usize>, prob: u8, bit: bool);
|
||||
}
|
||||
|
||||
struct CostSink(f32);
|
||||
impl TokenSink for CostSink {
|
||||
/// Adds the bit's cost.
|
||||
fn bit(&mut self, _: usize, _: usize, _: usize, _: Option<usize>, prob: u8, bit: bool) {
|
||||
self.0 += cost(prob, bit);
|
||||
}
|
||||
}
|
||||
|
||||
/// A subblock mode candidate: cost, mode, levels, reconstruction, squared error, bits.
|
||||
type BestSub = (f64, u8, [i16; 16], [u8; 16], u64, f32);
|
||||
|
||||
/// How often each token tree node saw a 0 and a 1: by block type, band, context and node.
|
||||
type TokenCounts = Box<[[[[[u32; 2]; 11]; 3]; 8]; 4]>;
|
||||
|
||||
struct CountSink(TokenCounts);
|
||||
impl TokenSink for CountSink {
|
||||
/// Counts the bit against its tree node, for choosing probabilities.
|
||||
fn bit(&mut self, ty: usize, band: usize, ctx: usize, node: Option<usize>, _: u8, bit: bool) {
|
||||
if let Some(n) = node {
|
||||
self.0[ty][band][ctx][n][usize::from(bit)] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenSink for BoolWriter {
|
||||
/// Writes the bit.
|
||||
fn bit(&mut self, _: usize, _: usize, _: usize, _: Option<usize>, prob: u8, bit: bool) {
|
||||
self.put(prob, bit);
|
||||
}
|
||||
}
|
||||
|
||||
/// Codes one token (§13.2): its tree path, extra bits and sign.
|
||||
fn token<S: TokenSink>(s: &mut S, probs: &Probs, ty: usize, band: usize, ctx: usize, v: i16, skip_eob: bool) {
|
||||
let p = &probs[ty][band][ctx];
|
||||
let node = |s: &mut S, n: usize, bit: bool| s.bit(ty, band, ctx, Some(n), p[n], bit);
|
||||
if !skip_eob {
|
||||
node(s, 0, true);
|
||||
}
|
||||
let a = u32::from(v.unsigned_abs());
|
||||
if a == 0 {
|
||||
node(s, 1, false);
|
||||
return;
|
||||
}
|
||||
node(s, 1, true);
|
||||
if a == 1 {
|
||||
node(s, 2, false);
|
||||
} else {
|
||||
node(s, 2, true);
|
||||
if a <= 4 {
|
||||
node(s, 3, false);
|
||||
if a == 2 {
|
||||
node(s, 4, false);
|
||||
} else {
|
||||
node(s, 4, true);
|
||||
node(s, 5, a == 4);
|
||||
}
|
||||
} else {
|
||||
node(s, 3, true);
|
||||
let cat = match a {
|
||||
5..=6 => 0,
|
||||
7..=10 => 1,
|
||||
11..=18 => 2,
|
||||
19..=34 => 3,
|
||||
35..=66 => 4,
|
||||
_ => 5,
|
||||
};
|
||||
if cat <= 1 {
|
||||
node(s, 6, false);
|
||||
node(s, 7, cat == 1);
|
||||
} else {
|
||||
node(s, 6, true);
|
||||
if cat <= 3 {
|
||||
node(s, 8, false);
|
||||
node(s, 9, cat == 3);
|
||||
} else {
|
||||
node(s, 8, true);
|
||||
node(s, 10, cat == 5);
|
||||
}
|
||||
}
|
||||
let extra = a - CAT_BASE[cat];
|
||||
let bits = PCAT[cat];
|
||||
for (k, &pp) in bits.iter().enumerate() {
|
||||
s.bit(ty, band, ctx, None, pp, (extra >> (bits.len() - 1 - k)) & 1 == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
s.bit(ty, band, ctx, None, 128, v < 0);
|
||||
}
|
||||
|
||||
/// Codes a block's coefficients from scan position `first` with neighbour context `ctx`;
|
||||
/// returns whether any of them is non-zero.
|
||||
fn code_block<S: TokenSink>(s: &mut S, probs: &Probs, levels: &[i16; 16], ty: usize, first: usize, ctx: usize) -> bool {
|
||||
let Some(last) = (first..16).rev().find(|&i| levels[ZIGZAG[i]] != 0) else {
|
||||
let band = COEFF_BANDS[first];
|
||||
s.bit(ty, band, ctx, Some(0), probs[ty][band][ctx][0], false);
|
||||
return false;
|
||||
};
|
||||
let mut c = ctx;
|
||||
let mut skip_eob = false;
|
||||
for i in first..=last {
|
||||
let v = levels[ZIGZAG[i]];
|
||||
token(s, probs, ty, COEFF_BANDS[i], c, v, skip_eob);
|
||||
let a = v.unsigned_abs();
|
||||
c = match a {
|
||||
0 => 0,
|
||||
1 => 1,
|
||||
_ => 2,
|
||||
};
|
||||
skip_eob = a == 0;
|
||||
}
|
||||
if last < 15 {
|
||||
let band = COEFF_BANDS[last + 1];
|
||||
s.bit(ty, band, c, Some(0), probs[ty][band][c][0], false);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// The bits a block of levels costs to code, from its first coefficient.
|
||||
fn block_cost(probs: &Probs, levels: &[i16; 16], ty: usize, first: usize, ctx: usize) -> f32 {
|
||||
let mut s = CostSink(0.0);
|
||||
code_block(&mut s, probs, levels, ty, first, ctx);
|
||||
s.0
|
||||
}
|
||||
|
||||
/// Whether any coefficient from `first` on is non-zero.
|
||||
fn any_coded(levels: &[i16; 16], first: usize) -> bool {
|
||||
(first..16).any(|i| levels[ZIGZAG[i]] != 0)
|
||||
}
|
||||
|
||||
/// Runs every macroblock's coefficients through `sink` with the contexts a decoder keeps.
|
||||
fn tokens<S: TokenSink>(s: &mut S, probs: &Probs, mbs: &[Mb], mbw: usize) {
|
||||
let mut above = vec![Nz::default(); mbw];
|
||||
let mut left = Nz::default();
|
||||
for (i, mb) in mbs.iter().enumerate() {
|
||||
let x = i % mbw;
|
||||
if x == 0 {
|
||||
left = Nz::default();
|
||||
}
|
||||
let Some(up) = above.get_mut(x) else { return };
|
||||
let has_y2 = mb.ymode != B_PRED;
|
||||
if mb.skip {
|
||||
// Nothing coded: every block is empty, and so is Y2 for a macroblock that has one.
|
||||
up.y = [false; 4];
|
||||
up.u = [false; 2];
|
||||
up.v = [false; 2];
|
||||
left.y = [false; 4];
|
||||
left.u = [false; 2];
|
||||
left.v = [false; 2];
|
||||
if has_y2 {
|
||||
up.y2 = false;
|
||||
left.y2 = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let (ty, first) = if has_y2 {
|
||||
let nz = code_block(s, probs, &mb.y2, TYPE_Y2, 0, usize::from(left.y2) + usize::from(up.y2));
|
||||
left.y2 = nz;
|
||||
up.y2 = nz;
|
||||
(TYPE_Y_AFTER_Y2, 1)
|
||||
} else {
|
||||
(TYPE_Y_WITH_DC, 0)
|
||||
};
|
||||
for (b, levels) in mb.y.iter().enumerate() {
|
||||
let (r, c) = (b / 4, b % 4);
|
||||
let nz = code_block(s, probs, levels, ty, first, usize::from(left.y[r]) + usize::from(up.y[c]));
|
||||
left.y[r] = nz;
|
||||
up.y[c] = nz;
|
||||
}
|
||||
for (b, levels) in mb.u.iter().enumerate() {
|
||||
let (r, c) = (b / 2, b % 2);
|
||||
let nz = code_block(s, probs, levels, TYPE_UV, 0, usize::from(left.u[r]) + usize::from(up.u[c]));
|
||||
left.u[r] = nz;
|
||||
up.u[c] = nz;
|
||||
}
|
||||
for (b, levels) in mb.v.iter().enumerate() {
|
||||
let (r, c) = (b / 2, b % 2);
|
||||
let nz = code_block(s, probs, levels, TYPE_UV, 0, usize::from(left.v[r]) + usize::from(up.v[c]));
|
||||
left.v[r] = nz;
|
||||
up.v[c] = nz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The probabilities to code the frame's tokens with: each default replaced where the frame's
|
||||
/// own counts save more than the update costs (§13.4).
|
||||
fn updated_probs(counts: &CountSink) -> Probs {
|
||||
let mut probs = DEFAULT_COEFF_PROBS;
|
||||
for t in 0..4 {
|
||||
for b in 0..8 {
|
||||
for c in 0..3 {
|
||||
for n in 0..11 {
|
||||
let [n0, n1] = counts.0[t][b][c][n];
|
||||
let total = n0 + n1;
|
||||
if total == 0 {
|
||||
continue;
|
||||
}
|
||||
let old = DEFAULT_COEFF_PROBS[t][b][c][n];
|
||||
let new = ((u64::from(n0) * 256 + u64::from(total) / 2) / u64::from(total)).clamp(1, 255) as u8;
|
||||
let bits = |p: u8| n0 as f32 * cost(p, false) + n1 as f32 * cost(p, true);
|
||||
let upd = COEFF_UPDATE_PROBS[t][b][c][n];
|
||||
let keep = bits(old) + cost(upd, false);
|
||||
let change = bits(new) + cost(upd, true) + 8.0;
|
||||
if new != old && change < keep {
|
||||
probs[t][b][c][n] = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
probs
|
||||
}
|
||||
|
||||
struct Encoder<'a> {
|
||||
src_y: &'a Plane,
|
||||
src_u: &'a Plane,
|
||||
src_v: &'a Plane,
|
||||
rec_y: Plane,
|
||||
rec_u: Plane,
|
||||
rec_v: Plane,
|
||||
mbw: usize,
|
||||
q: Quant,
|
||||
lambda: f64,
|
||||
probs: Probs,
|
||||
above_nz: Vec<Nz>,
|
||||
left_nz: Nz,
|
||||
above_bmodes: Vec<[u8; 4]>,
|
||||
left_bmodes: [u8; 4],
|
||||
}
|
||||
|
||||
/// A candidate luma coding of a macroblock.
|
||||
struct Luma {
|
||||
mode: u8,
|
||||
bmodes: [u8; 16],
|
||||
y2: [i16; 16],
|
||||
y: [[i16; 16]; 16],
|
||||
recon: [u8; 256],
|
||||
sse: u64,
|
||||
bits: f32,
|
||||
}
|
||||
|
||||
/// A candidate chroma coding.
|
||||
struct Chroma {
|
||||
mode: u8,
|
||||
u: [[i16; 16]; 4],
|
||||
v: [[i16; 16]; 4],
|
||||
recon_u: [u8; 64],
|
||||
recon_v: [u8; 64],
|
||||
sse: u64,
|
||||
bits: f32,
|
||||
}
|
||||
|
||||
/// `v` clamped to a sample.
|
||||
fn clamp255(v: i32) -> u8 {
|
||||
v.clamp(0, 255) as u8
|
||||
}
|
||||
|
||||
impl Encoder<'_> {
|
||||
/// The rate-distortion cost: the squared error plus λ times the bits.
|
||||
fn cost_of(&self, sse: u64, bits: f32) -> f64 {
|
||||
sse as f64 + self.lambda * f64::from(bits)
|
||||
}
|
||||
|
||||
/// The 16×16 luma edges, and the four pixels above and to the right (§12.3).
|
||||
fn luma_edges(&self, mbx: usize, mby: usize) -> (Edges<16>, [u8; 4]) {
|
||||
let (x0, y0) = (mbx * 16, mby * 16);
|
||||
let rec = &self.rec_y;
|
||||
let above: [u8; 16] = std::array::from_fn(|i| if mby == 0 { 127 } else { rec.get(x0 + i, y0 - 1) });
|
||||
let left: [u8; 16] = std::array::from_fn(|i| if mbx == 0 { 129 } else { rec.get(x0 - 1, y0 + i) });
|
||||
let corner = if mby == 0 {
|
||||
127
|
||||
} else if mbx == 0 {
|
||||
129
|
||||
} else {
|
||||
rec.get(x0 - 1, y0 - 1)
|
||||
};
|
||||
let right: [u8; 4] = std::array::from_fn(|i| {
|
||||
if mby == 0 {
|
||||
127
|
||||
} else if mbx + 1 == self.mbw {
|
||||
rec.get(x0 + 15, y0 - 1)
|
||||
} else {
|
||||
rec.get(x0 + 16 + i, y0 - 1)
|
||||
}
|
||||
});
|
||||
(Edges { above, left, corner, have_above: mby > 0, have_left: mbx > 0 }, right)
|
||||
}
|
||||
|
||||
/// The reconstructed edges an 8×8 chroma block predicts from, with the spec's 127 / 129 outside the frame.
|
||||
fn chroma_edges(rec: &Plane, mbx: usize, mby: usize) -> Edges<8> {
|
||||
let (x0, y0) = (mbx * 8, mby * 8);
|
||||
let above: [u8; 8] = std::array::from_fn(|i| if mby == 0 { 127 } else { rec.get(x0 + i, y0 - 1) });
|
||||
let left: [u8; 8] = std::array::from_fn(|i| if mbx == 0 { 129 } else { rec.get(x0 - 1, y0 + i) });
|
||||
let corner = if mby == 0 {
|
||||
127
|
||||
} else if mbx == 0 {
|
||||
129
|
||||
} else {
|
||||
rec.get(x0 - 1, y0 - 1)
|
||||
};
|
||||
Edges { above, left, corner, have_above: mby > 0, have_left: mbx > 0 }
|
||||
}
|
||||
|
||||
/// A 4×4 block of a plane minus a prediction.
|
||||
fn residual(src: &Plane, x0: usize, y0: usize, pred: &[u8], stride: usize) -> [i32; 16] {
|
||||
std::array::from_fn(|i| {
|
||||
let (r, c) = (i / 4, i % 4);
|
||||
i32::from(src.get(x0 + c, y0 + r)) - i32::from(pred[r * stride + c])
|
||||
})
|
||||
}
|
||||
|
||||
/// Code the macroblock's luma with one 16×16 prediction `mode`: its residual through Y2 and its cost.
|
||||
fn whole_luma(&self, mbx: usize, mby: usize, mode: u8, e: &Edges<16>) -> Luma {
|
||||
let (x0, y0) = (mbx * 16, mby * 16);
|
||||
let mut pred = [0u8; 256];
|
||||
predict_block(mode, e, &mut pred);
|
||||
let coefs: [[f64; 16]; 16] = std::array::from_fn(|b| {
|
||||
let (r, c) = (b / 4, b % 4);
|
||||
fdct(&Self::residual(self.src_y, x0 + c * 4, y0 + r * 4, &pred[r * 64 + c * 4..], 16))
|
||||
});
|
||||
let dc: [f64; 16] = std::array::from_fn(|b| coefs[b][0]);
|
||||
let y2c = fwht(&dc);
|
||||
let y2: [i16; 16] = std::array::from_fn(|i| quantize(y2c[i], self.q.y2[usize::from(i > 0)], i == 0));
|
||||
let y: [[i16; 16]; 16] =
|
||||
std::array::from_fn(|b| std::array::from_fn(|i| if i == 0 { 0 } else { quantize(coefs[b][i], self.q.y1[1], false) }));
|
||||
let y2dq: [i16; 16] = std::array::from_fn(|i| dequant(y2[i], self.q.y2[usize::from(i > 0)]));
|
||||
let dcs = iwht(&y2dq);
|
||||
let mut recon = [0u8; 256];
|
||||
let mut sse = 0u64;
|
||||
let (mut ly, mut ay) = (self.left_nz.y, self.above_nz.get(mbx).copied().unwrap_or_default().y);
|
||||
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
|
||||
let mut bits = tree_cost(&KF_YMODE_TREE, &KF_YMODE_PROB, mode)
|
||||
+ block_cost(&self.probs, &y2, TYPE_Y2, 0, usize::from(self.left_nz.y2) + usize::from(up.y2));
|
||||
for b in 0..16 {
|
||||
let (r, c) = (b / 4, b % 4);
|
||||
let mut d: [i16; 16] = std::array::from_fn(|i| dequant(y[b][i], self.q.y1[1]));
|
||||
d[0] = dcs[b];
|
||||
let res = idct(&d);
|
||||
for i in 0..16 {
|
||||
let (rr, cc) = (r * 4 + i / 4, c * 4 + i % 4);
|
||||
let v = clamp255(i32::from(pred[rr * 16 + cc]) + i32::from(res[i]));
|
||||
recon[rr * 16 + cc] = v;
|
||||
let diff = i64::from(v) - i64::from(self.src_y.get(x0 + cc, y0 + rr));
|
||||
sse += (diff * diff) as u64;
|
||||
}
|
||||
bits += block_cost(&self.probs, &y[b], TYPE_Y_AFTER_Y2, 1, usize::from(ly[r]) + usize::from(ay[c]));
|
||||
let nz = any_coded(&y[b], 1);
|
||||
ly[r] = nz;
|
||||
ay[c] = nz;
|
||||
}
|
||||
Luma { mode, bmodes: [implied_bmode(mode); 16], y2, y, recon, sse, bits }
|
||||
}
|
||||
|
||||
/// B_PRED: each subblock with its best mode, in order, each predicted from the ones before.
|
||||
/// Gives up (None) once it costs more than `limit`.
|
||||
fn sub_luma(&self, mbx: usize, mby: usize, e: &Edges<16>, right: &[u8; 4], limit: f64) -> Option<Luma> {
|
||||
let (x0, y0) = (mbx * 16, mby * 16);
|
||||
let mut wr = [0u8; 256];
|
||||
let mut bm = [0u8; 16];
|
||||
let mut y = [[0i16; 16]; 16];
|
||||
let (mut ly, mut ay) = (self.left_nz.y, self.above_nz.get(mbx).copied().unwrap_or_default().y);
|
||||
let above_modes = self.above_bmodes.get(mbx).copied().unwrap_or([B_DC_PRED; 4]);
|
||||
let mut bits = tree_cost(&KF_YMODE_TREE, &KF_YMODE_PROB, B_PRED);
|
||||
let mut sse = 0u64;
|
||||
for s in 0..16 {
|
||||
let (r, c) = (s / 4, s % 4);
|
||||
let a: [u8; 8] = std::array::from_fn(|i| {
|
||||
if i < 4 {
|
||||
if r == 0 { e.above[c * 4 + i] } else { wr[(r * 4 - 1) * 16 + c * 4 + i] }
|
||||
} else if c == 3 {
|
||||
right[i - 4]
|
||||
} else if r == 0 {
|
||||
e.above[c * 4 + i]
|
||||
} else {
|
||||
wr[(r * 4 - 1) * 16 + c * 4 + i]
|
||||
}
|
||||
});
|
||||
let l: [u8; 4] = std::array::from_fn(|i| if c == 0 { e.left[r * 4 + i] } else { wr[(r * 4 + i) * 16 + c * 4 - 1] });
|
||||
let p = match (r, c) {
|
||||
(0, 0) => e.corner,
|
||||
(0, _) => e.above[c * 4 - 1],
|
||||
(_, 0) => e.left[r * 4 - 1],
|
||||
_ => wr[(r * 4 - 1) * 16 + c * 4 - 1],
|
||||
};
|
||||
let am = if r == 0 { above_modes[c] } else { bm[s - 4] };
|
||||
let lm = if c == 0 { self.left_bmodes[r] } else { bm[s - 1] };
|
||||
let mode_probs = &KF_BMODE_PROB[usize::from(am)][usize::from(lm)];
|
||||
let ctx = usize::from(ly[r]) + usize::from(ay[c]);
|
||||
let mut best: Option<BestSub> = None;
|
||||
for m in 0..10u8 {
|
||||
let pred = predict_sub(m, &a, &l, p);
|
||||
let coefs = fdct(&Self::residual(self.src_y, x0 + c * 4, y0 + r * 4, &pred, 4));
|
||||
let lv: [i16; 16] = std::array::from_fn(|i| quantize(coefs[i], self.q.y1[usize::from(i > 0)], i == 0));
|
||||
let d: [i16; 16] = std::array::from_fn(|i| dequant(lv[i], self.q.y1[usize::from(i > 0)]));
|
||||
let res = idct(&d);
|
||||
let mut out = [0u8; 16];
|
||||
let mut e2 = 0u64;
|
||||
for i in 0..16 {
|
||||
out[i] = clamp255(i32::from(pred[i]) + i32::from(res[i]));
|
||||
let diff = i64::from(out[i]) - i64::from(self.src_y.get(x0 + c * 4 + i % 4, y0 + r * 4 + i / 4));
|
||||
e2 += (diff * diff) as u64;
|
||||
}
|
||||
let b = tree_cost(&BMODE_TREE, mode_probs, m) + block_cost(&self.probs, &lv, TYPE_Y_WITH_DC, 0, ctx);
|
||||
let j = self.cost_of(e2, b);
|
||||
if best.as_ref().is_none_or(|bst| j < bst.0) {
|
||||
best = Some((j, m, lv, out, e2, b));
|
||||
}
|
||||
}
|
||||
let (_, m, lv, out, e2, b) = best?;
|
||||
for i in 0..16 {
|
||||
wr[(r * 4 + i / 4) * 16 + c * 4 + i % 4] = out[i];
|
||||
}
|
||||
bm[s] = m;
|
||||
y[s] = lv;
|
||||
sse += e2;
|
||||
bits += b;
|
||||
let nz = any_coded(&lv, 0);
|
||||
ly[r] = nz;
|
||||
ay[c] = nz;
|
||||
if self.cost_of(sse, bits) > limit {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(Luma { mode: B_PRED, bmodes: bm, y2: [0; 16], y, recon: wr, sse, bits })
|
||||
}
|
||||
|
||||
/// Code the macroblock's U and V with prediction `mode`: their levels, reconstruction and cost.
|
||||
fn chroma(&self, mbx: usize, mby: usize, mode: u8) -> Chroma {
|
||||
let (x0, y0) = (mbx * 8, mby * 8);
|
||||
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
|
||||
let mut bits = tree_cost(&UV_MODE_TREE, &KF_UV_MODE_PROB, mode);
|
||||
let mut sse = 0u64;
|
||||
let mut plane = |src: &Plane, rec: &Plane, lnz: [bool; 2], anz: [bool; 2]| -> ([[i16; 16]; 4], [u8; 64]) {
|
||||
let e = Self::chroma_edges(rec, mbx, mby);
|
||||
let mut pred = [0u8; 64];
|
||||
predict_block(mode, &e, &mut pred);
|
||||
let mut levels = [[0i16; 16]; 4];
|
||||
let mut recon = [0u8; 64];
|
||||
let (mut l, mut a) = (lnz, anz);
|
||||
for b in 0..4 {
|
||||
let (r, c) = (b / 2, b % 2);
|
||||
let coefs = fdct(&Self::residual(src, x0 + c * 4, y0 + r * 4, &pred[r * 32 + c * 4..], 8));
|
||||
let lv: [i16; 16] = std::array::from_fn(|i| quantize(coefs[i], self.q.uv[usize::from(i > 0)], i == 0));
|
||||
let d: [i16; 16] = std::array::from_fn(|i| dequant(lv[i], self.q.uv[usize::from(i > 0)]));
|
||||
let res = idct(&d);
|
||||
for i in 0..16 {
|
||||
let (rr, cc) = (r * 4 + i / 4, c * 4 + i % 4);
|
||||
let v = clamp255(i32::from(pred[rr * 8 + cc]) + i32::from(res[i]));
|
||||
recon[rr * 8 + cc] = v;
|
||||
let diff = i64::from(v) - i64::from(src.get(x0 + cc, y0 + rr));
|
||||
sse += (diff * diff) as u64;
|
||||
}
|
||||
bits += block_cost(&self.probs, &lv, TYPE_UV, 0, usize::from(l[r]) + usize::from(a[c]));
|
||||
let nz = any_coded(&lv, 0);
|
||||
l[r] = nz;
|
||||
a[c] = nz;
|
||||
levels[b] = lv;
|
||||
}
|
||||
(levels, recon)
|
||||
};
|
||||
let (u, recon_u) = plane(self.src_u, &self.rec_u, self.left_nz.u, up.u);
|
||||
let (v, recon_v) = plane(self.src_v, &self.rec_v, self.left_nz.v, up.v);
|
||||
Chroma { mode, u, v, recon_u, recon_v, sse, bits }
|
||||
}
|
||||
|
||||
/// Chooses, codes and reconstructs macroblock (mbx, mby).
|
||||
fn macroblock(&mut self, mbx: usize, mby: usize) -> Mb {
|
||||
let (e, right) = self.luma_edges(mbx, mby);
|
||||
let mut luma: Option<Luma> = None;
|
||||
for mode in [DC_PRED, V_PRED, H_PRED, TM_PRED] {
|
||||
let cand = self.whole_luma(mbx, mby, mode, &e);
|
||||
if luma.as_ref().is_none_or(|l| self.cost_of(cand.sse, cand.bits) < self.cost_of(l.sse, l.bits)) {
|
||||
luma = Some(cand);
|
||||
}
|
||||
}
|
||||
let limit = luma.as_ref().map_or(f64::INFINITY, |l| self.cost_of(l.sse, l.bits));
|
||||
if let Some(sub) = self.sub_luma(mbx, mby, &e, &right, limit) {
|
||||
luma = Some(sub);
|
||||
}
|
||||
let mut chroma: Option<Chroma> = None;
|
||||
for mode in [DC_PRED, V_PRED, H_PRED, TM_PRED] {
|
||||
let cand = self.chroma(mbx, mby, mode);
|
||||
if chroma.as_ref().is_none_or(|c| self.cost_of(cand.sse, cand.bits) < self.cost_of(c.sse, c.bits)) {
|
||||
chroma = Some(cand);
|
||||
}
|
||||
}
|
||||
let luma = luma.unwrap_or_else(|| self.whole_luma(mbx, mby, DC_PRED, &e));
|
||||
let chroma = chroma.unwrap_or_else(|| self.chroma(mbx, mby, DC_PRED));
|
||||
|
||||
// The reconstruction later macroblocks are predicted from.
|
||||
for i in 0..256 {
|
||||
self.rec_y.set(mbx * 16 + i % 16, mby * 16 + i / 16, luma.recon[i]);
|
||||
}
|
||||
for i in 0..64 {
|
||||
self.rec_u.set(mbx * 8 + i % 8, mby * 8 + i / 8, chroma.recon_u[i]);
|
||||
self.rec_v.set(mbx * 8 + i % 8, mby * 8 + i / 8, chroma.recon_v[i]);
|
||||
}
|
||||
|
||||
// Contexts, as the token pass will keep them.
|
||||
let has_y2 = luma.mode != B_PRED;
|
||||
let first = usize::from(has_y2);
|
||||
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
|
||||
let mut nz = Nz { y2: if has_y2 { any_coded(&luma.y2, 0) } else { up.y2 }, ..Nz::default() };
|
||||
let mut left = self.left_nz;
|
||||
if has_y2 {
|
||||
left.y2 = nz.y2;
|
||||
}
|
||||
for b in 0..16 {
|
||||
let coded = any_coded(&luma.y[b], first);
|
||||
left.y[b / 4] = coded;
|
||||
nz.y[b % 4] = coded;
|
||||
}
|
||||
for b in 0..4 {
|
||||
let cu = any_coded(&chroma.u[b], 0);
|
||||
let cv = any_coded(&chroma.v[b], 0);
|
||||
left.u[b / 2] = cu;
|
||||
nz.u[b % 2] = cu;
|
||||
left.v[b / 2] = cv;
|
||||
nz.v[b % 2] = cv;
|
||||
}
|
||||
if !has_y2 {
|
||||
left.y2 = self.left_nz.y2;
|
||||
}
|
||||
self.left_nz = left;
|
||||
if let Some(a) = self.above_nz.get_mut(mbx) {
|
||||
*a = nz;
|
||||
}
|
||||
if let Some(a) = self.above_bmodes.get_mut(mbx) {
|
||||
*a = [luma.bmodes[12], luma.bmodes[13], luma.bmodes[14], luma.bmodes[15]];
|
||||
}
|
||||
self.left_bmodes = [luma.bmodes[3], luma.bmodes[7], luma.bmodes[11], luma.bmodes[15]];
|
||||
|
||||
let skip = (!has_y2 || !any_coded(&luma.y2, 0))
|
||||
&& luma.y.iter().all(|b| !any_coded(b, first))
|
||||
&& chroma.u.iter().chain(chroma.v.iter()).all(|b| !any_coded(b, 0));
|
||||
Mb { ymode: luma.mode, bmodes: luma.bmodes, uvmode: chroma.mode, skip, y2: luma.y2, y: luma.y, u: chroma.u, v: chroma.v }
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes planes already at the coded size (whole macroblocks).
|
||||
pub(crate) fn encode(y: &Plane, u: &Plane, v: &Plane, width: u32, height: u32, params: &Vp8Params) -> Result<Vp8Frame, Vp8Error> {
|
||||
let (mbw, mbh) = (y.width / 16, y.height / 16);
|
||||
let q = q_index(params.quality);
|
||||
let quant = Quant::new(q);
|
||||
let lf = params.loop_filter.unwrap_or_else(|| ((f64::from(q) * 0.4).round() as u8).min(63)).min(63);
|
||||
let mut enc = Encoder {
|
||||
src_y: y,
|
||||
src_u: u,
|
||||
src_v: v,
|
||||
rec_y: Plane::new(y.width, y.height),
|
||||
rec_u: Plane::new(u.width, u.height),
|
||||
rec_v: Plane::new(v.width, v.height),
|
||||
mbw,
|
||||
q: quant,
|
||||
lambda: LAMBDA_PER_STEP2 * f64::from(quant.y1[1]).powi(2),
|
||||
probs: DEFAULT_COEFF_PROBS,
|
||||
above_nz: vec![Nz::default(); mbw],
|
||||
left_nz: Nz::default(),
|
||||
above_bmodes: vec![[B_DC_PRED; 4]; mbw],
|
||||
left_bmodes: [B_DC_PRED; 4],
|
||||
};
|
||||
let mut mbs = Vec::with_capacity(mbw * mbh);
|
||||
for mby in 0..mbh {
|
||||
enc.left_nz = Nz::default();
|
||||
enc.left_bmodes = [B_DC_PRED; 4];
|
||||
for mbx in 0..mbw {
|
||||
mbs.push(enc.macroblock(mbx, mby));
|
||||
}
|
||||
}
|
||||
|
||||
// Token counts → probability updates.
|
||||
let mut counts = CountSink(Box::new([[[[[0; 2]; 11]; 3]; 8]; 4]));
|
||||
tokens(&mut counts, &DEFAULT_COEFF_PROBS, &mbs, mbw);
|
||||
let probs = updated_probs(&counts);
|
||||
|
||||
// First partition: the frame header (Annex A §19.2) and the macroblock headers (§19.3).
|
||||
let skipped = mbs.iter().filter(|m| m.skip).count();
|
||||
let use_skip = skipped > 0;
|
||||
let prob_skip = (((mbs.len() - skipped) * 256 + mbs.len() / 2) / mbs.len().max(1)).clamp(1, 255) as u8;
|
||||
let mut w = BoolWriter::new();
|
||||
w.literal(1, 0); // color_space: BT.601
|
||||
w.literal(1, 0); // clamping_type: the decoder clamps
|
||||
w.literal(1, 0); // segmentation_enabled
|
||||
w.literal(1, 0); // filter_type: normal
|
||||
w.literal(6, u32::from(lf));
|
||||
w.literal(3, 0); // sharpness_level
|
||||
w.literal(1, 0); // loop_filter_adj_enable
|
||||
w.literal(2, 0); // log2_nbr_of_dct_partitions: one
|
||||
w.literal(7, u32::from(q));
|
||||
for _ in 0..5 {
|
||||
w.literal(1, 0); // no quantizer index deltas
|
||||
}
|
||||
w.literal(1, 0); // refresh_entropy_probs
|
||||
for t in 0..4 {
|
||||
for b in 0..8 {
|
||||
for c in 0..3 {
|
||||
for n in 0..11 {
|
||||
let new = probs[t][b][c][n];
|
||||
let changed = new != DEFAULT_COEFF_PROBS[t][b][c][n];
|
||||
w.put(COEFF_UPDATE_PROBS[t][b][c][n], changed);
|
||||
if changed {
|
||||
w.literal(8, u32::from(new));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
w.literal(1, u32::from(use_skip));
|
||||
if use_skip {
|
||||
w.literal(8, u32::from(prob_skip));
|
||||
}
|
||||
let mut above_modes = vec![[B_DC_PRED; 4]; mbw];
|
||||
let mut left_modes = [B_DC_PRED; 4];
|
||||
for (i, mb) in mbs.iter().enumerate() {
|
||||
let x = i % mbw;
|
||||
if x == 0 {
|
||||
left_modes = [B_DC_PRED; 4];
|
||||
}
|
||||
if use_skip {
|
||||
w.put(prob_skip, mb.skip);
|
||||
}
|
||||
put_tree(&mut w, &KF_YMODE_TREE, &KF_YMODE_PROB, mb.ymode);
|
||||
let up = above_modes.get(x).copied().unwrap_or([B_DC_PRED; 4]);
|
||||
if mb.ymode == B_PRED {
|
||||
for s in 0..16 {
|
||||
let (r, c) = (s / 4, s % 4);
|
||||
let am = if r == 0 { up[c] } else { mb.bmodes[s - 4] };
|
||||
let lm = if c == 0 { left_modes[r] } else { mb.bmodes[s - 1] };
|
||||
put_tree(&mut w, &BMODE_TREE, &KF_BMODE_PROB[usize::from(am)][usize::from(lm)], mb.bmodes[s]);
|
||||
}
|
||||
}
|
||||
put_tree(&mut w, &UV_MODE_TREE, &KF_UV_MODE_PROB, mb.uvmode);
|
||||
if let Some(a) = above_modes.get_mut(x) {
|
||||
*a = [mb.bmodes[12], mb.bmodes[13], mb.bmodes[14], mb.bmodes[15]];
|
||||
}
|
||||
left_modes = [mb.bmodes[3], mb.bmodes[7], mb.bmodes[11], mb.bmodes[15]];
|
||||
}
|
||||
let first = w.finish();
|
||||
if first.len() >= 1 << 19 {
|
||||
return Err(Vp8Error::PartitionTooLarge(first.len()));
|
||||
}
|
||||
|
||||
let mut t = BoolWriter::new();
|
||||
tokens(&mut t, &probs, &mbs, mbw);
|
||||
let second = t.finish();
|
||||
|
||||
// Frame tag (key frame, version 0, shown) and the key frame start code and size (§9.1).
|
||||
let tag = (1u32 << 4) | ((first.len() as u32) << 5);
|
||||
let mut data = Vec::with_capacity(10 + first.len() + second.len());
|
||||
data.extend_from_slice(&tag.to_le_bytes()[..3]);
|
||||
data.extend_from_slice(&[0x9d, 0x01, 0x2a]);
|
||||
data.extend_from_slice(&((width & 0x3fff) as u16).to_le_bytes());
|
||||
data.extend_from_slice(&((height & 0x3fff) as u16).to_le_bytes());
|
||||
data.extend_from_slice(&first);
|
||||
data.extend_from_slice(&second);
|
||||
Ok(Vp8Frame { data, y: enc.rec_y, u: enc.rec_u, v: enc.rec_v, q_index: q, loop_filter: lf })
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//! A VP8 key-frame encoder: the lossy picture of a WebP image or animation frame, written from
|
||||
//! RFC 6386 (*VP8 Data Format and Decoding Guide*, November 2011) by mirroring its decoding
|
||||
//! process (see README.md).
|
||||
//!
|
||||
//! * Every macroblock is predicted with the best of the four 16×16 luma modes (with the Y2
|
||||
//! Walsh–Hadamard block of DCs) or B_PRED (each 4×4 subblock with the best of its ten modes),
|
||||
//! and the best of the four 8×8 chroma modes, chosen by rate–distortion cost: squared error
|
||||
//! plus λ times the bits, the bits counted from the token and mode probabilities.
|
||||
//! * Coefficients are quantized from one quality setting (0–100 → the frame's quantizer index);
|
||||
//! macroblocks left with nothing to code are skipped.
|
||||
//! * The coefficient probabilities are updated per frame where the frame's own token counts pay
|
||||
//! for the update (§13.4).
|
||||
//! * The loop filter level is chosen from the quantizer (it shapes only the decoded picture: intra
|
||||
//! prediction reads the unfiltered reconstruction, §12).
|
||||
//!
|
||||
//! The encoder reconstructs exactly what a decoder reconstructs before loop filtering — the
|
||||
//! prediction, the 16-bit inverse transforms of §14 — and returns it with the frame.
|
||||
|
||||
mod bool;
|
||||
mod frame;
|
||||
mod predict;
|
||||
mod tables;
|
||||
mod transform;
|
||||
mod yuv;
|
||||
|
||||
pub use frame::Plane;
|
||||
|
||||
/// Largest width or height: the key frame header stores 14 bits.
|
||||
pub const MAX_SIDE: u32 = (1 << 14) - 1;
|
||||
|
||||
/// What to encode with.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Vp8Params {
|
||||
/// 0 (smallest) to 100 (best).
|
||||
pub quality: u8,
|
||||
/// The loop filter level (0–63, 0 = off); `None` chooses one from the quantizer.
|
||||
pub loop_filter: Option<u8>,
|
||||
}
|
||||
|
||||
impl Default for Vp8Params {
|
||||
/// Quality 90, the loop filter chosen from the quantizer.
|
||||
fn default() -> Self {
|
||||
Vp8Params { quality: 90, loop_filter: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// An encoded key frame.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Vp8Frame {
|
||||
/// The VP8 frame: frame tag, key frame header, first partition, token partition — what a
|
||||
/// WebP `VP8 ` chunk holds.
|
||||
pub data: Vec<u8>,
|
||||
/// What a decoder reconstructs before loop filtering, at the coded (macroblock-padded) size.
|
||||
pub y: Plane,
|
||||
pub u: Plane,
|
||||
pub v: Plane,
|
||||
/// The quantizer index used (0–127).
|
||||
pub q_index: u8,
|
||||
/// The loop filter level signalled.
|
||||
pub loop_filter: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum Vp8Error {
|
||||
#[error("VP8 frames are 1 to {MAX_SIDE} pixels a side (this one is {0}×{1})")]
|
||||
Size(u32, u32),
|
||||
#[error("{0} bytes of RGBA for a {1}×{2} frame")]
|
||||
Buffer(usize, u32, u32),
|
||||
#[error("the first partition is {0} bytes, over the 19-bit limit")]
|
||||
PartitionTooLarge(usize),
|
||||
}
|
||||
|
||||
/// Encodes straight (unpremultiplied) 8-bit RGBA as one VP8 key frame. Alpha is not part of a
|
||||
/// VP8 frame (a WebP file carries it beside the frame); it weights the colour of the chroma
|
||||
/// samples, so fully transparent pixels do not bleed into the edges of what is visible.
|
||||
pub fn encode_rgba(rgba: &[u8], width: u32, height: u32, params: &Vp8Params) -> Result<Vp8Frame, Vp8Error> {
|
||||
if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
|
||||
return Err(Vp8Error::Size(width, height));
|
||||
}
|
||||
let n = (width as usize).checked_mul(height as usize).and_then(|n| n.checked_mul(4));
|
||||
if n != Some(rgba.len()) {
|
||||
return Err(Vp8Error::Buffer(rgba.len(), width, height));
|
||||
}
|
||||
let (y, u, v) = yuv::rgba_to_yuv420(rgba, width, height);
|
||||
frame::encode(&y, &u, &v, width, height, params)
|
||||
}
|
||||
|
||||
/// Quality → quantizer index, interpolated between these points. Fitted on rendered motion
|
||||
/// graphics frames so that a quality gives about the PSNR (within half a decibel) and size the same
|
||||
/// quality gives in the widely used libwebp encoder; steep at the top, where each step of quality
|
||||
/// costs the most bytes.
|
||||
const QUALITY_TO_Q: [(u8, u8); 13] =
|
||||
[(0, 105), (10, 76), (20, 66), (30, 58), (40, 47), (50, 42), (60, 36), (70, 31), (80, 22), (85, 15), (90, 7), (95, 2), (100, 0)];
|
||||
|
||||
/// The quantizer index for a quality: 100 is index 0 (the finest steps).
|
||||
pub fn q_index(quality: u8) -> u8 {
|
||||
let q = quality.min(100);
|
||||
for pair in QUALITY_TO_Q.windows(2) {
|
||||
let [(q0, i0), (q1, i1)] = [pair[0], pair[1]];
|
||||
if q <= q1 {
|
||||
let t = f64::from(q - q0) / f64::from(q1 - q0);
|
||||
return (f64::from(i0) + t * (f64::from(i1) - f64::from(i0))).round() as u8;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
//! Intra prediction, RFC 6386 §12: the whole-block modes for 16×16 luma and 8×8 chroma, and the
|
||||
//! ten 4×4 subblock modes.
|
||||
|
||||
/// Whole-block modes, numbered as `intra_mbmode` (§11.2).
|
||||
pub(crate) const DC_PRED: u8 = 0;
|
||||
pub(crate) const V_PRED: u8 = 1;
|
||||
pub(crate) const H_PRED: u8 = 2;
|
||||
pub(crate) const TM_PRED: u8 = 3;
|
||||
pub(crate) const B_PRED: u8 = 4;
|
||||
|
||||
/// Subblock modes, numbered as `intra_bmode` (§11.2).
|
||||
pub(crate) const B_DC_PRED: u8 = 0;
|
||||
pub(crate) const B_TM_PRED: u8 = 1;
|
||||
pub(crate) const B_VE_PRED: u8 = 2;
|
||||
pub(crate) const B_HE_PRED: u8 = 3;
|
||||
pub(crate) const B_LD_PRED: u8 = 4;
|
||||
pub(crate) const B_RD_PRED: u8 = 5;
|
||||
pub(crate) const B_VR_PRED: u8 = 6;
|
||||
pub(crate) const B_VL_PRED: u8 = 7;
|
||||
pub(crate) const B_HD_PRED: u8 = 8;
|
||||
pub(crate) const B_HU_PRED: u8 = 9;
|
||||
|
||||
/// The pixels a block is predicted from, with the frame-edge values of §12 already in place:
|
||||
/// `above` (and, for 16×16 luma, the four above-right pixels after it), `left`, the corner above
|
||||
/// and to the left, and whether the real neighbours exist (for DC prediction's averages).
|
||||
pub(crate) struct Edges<const N: usize> {
|
||||
pub above: [u8; N],
|
||||
pub left: [u8; N],
|
||||
pub corner: u8,
|
||||
pub have_above: bool,
|
||||
pub have_left: bool,
|
||||
}
|
||||
|
||||
/// Predicts an N×N whole block (N = 16 luma, 8 chroma) in `mode` (DC, V, H or TM).
|
||||
pub(crate) fn predict_block<const N: usize>(mode: u8, e: &Edges<N>, out: &mut [u8]) {
|
||||
match mode {
|
||||
V_PRED => {
|
||||
for r in 0..N {
|
||||
out[r * N..r * N + N].copy_from_slice(&e.above);
|
||||
}
|
||||
}
|
||||
H_PRED => {
|
||||
for r in 0..N {
|
||||
out[r * N..r * N + N].fill(e.left[r]);
|
||||
}
|
||||
}
|
||||
TM_PRED => {
|
||||
for r in 0..N {
|
||||
for c in 0..N {
|
||||
out[r * N + c] = clamp(i32::from(e.left[r]) + i32::from(e.above[c]) - i32::from(e.corner));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let shift = N.trailing_zeros();
|
||||
let sum_a: u32 = e.above.iter().map(|&v| u32::from(v)).sum();
|
||||
let sum_l: u32 = e.left.iter().map(|&v| u32::from(v)).sum();
|
||||
let dc = match (e.have_above, e.have_left) {
|
||||
(true, true) => (sum_a + sum_l + (1 << shift)) >> (shift + 1),
|
||||
(true, false) => (sum_a + (1 << (shift - 1))) >> shift,
|
||||
(false, true) => (sum_l + (1 << (shift - 1))) >> shift,
|
||||
(false, false) => 128,
|
||||
};
|
||||
out[..N * N].fill(dc as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `v` clamped to a sample.
|
||||
fn clamp(v: i32) -> u8 {
|
||||
v.clamp(0, 255) as u8
|
||||
}
|
||||
|
||||
/// The weighted average of three neighbours, `(x + 2y + z + 2) / 4`.
|
||||
fn avg3(x: u8, y: u8, z: u8) -> u8 {
|
||||
((u32::from(x) + 2 * u32::from(y) + u32::from(z) + 2) >> 2) as u8
|
||||
}
|
||||
|
||||
/// The average of two neighbours, rounded up.
|
||||
fn avg2(x: u8, y: u8) -> u8 {
|
||||
((u32::from(x) + u32::from(y) + 1) >> 1) as u8
|
||||
}
|
||||
|
||||
/// Predicts a 4×4 subblock in `mode` from `a` (the 8 pixels above: 4 over the block, then 4 above
|
||||
/// and to its right), `l` (the 4 to its left) and `p` (above-left), as §12.3's
|
||||
/// `subblock_intra_predict`.
|
||||
pub(crate) fn predict_sub(mode: u8, a: &[u8; 8], l: &[u8; 4], p: u8) -> [u8; 16] {
|
||||
let mut b = [0u8; 16];
|
||||
let mut set = |r: usize, c: usize, v: u8| b[r * 4 + c] = v;
|
||||
// E: left column bottom to top, the corner, then the row above.
|
||||
let e = [l[3], l[2], l[1], l[0], p, a[0], a[1], a[2], a[3]];
|
||||
// avg3p(A + j) for j = -1..=6 (A[-1] is the corner).
|
||||
let at = |j: i32| if j < 0 { p } else { a[j as usize] };
|
||||
let a3 = |j: i32| avg3(at(j - 1), at(j), at(j + 1));
|
||||
let e3 = |j: usize| avg3(e[j - 1], e[j], e[j + 1]);
|
||||
let e2 = |j: usize| avg2(e[j], e[j + 1]);
|
||||
match mode {
|
||||
B_TM_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
set(r, c, clamp(i32::from(l[r]) + i32::from(a[c]) - i32::from(p)));
|
||||
}
|
||||
}
|
||||
}
|
||||
B_VE_PRED => {
|
||||
for c in 0..4 {
|
||||
let v = a3(c as i32);
|
||||
for r in 0..4 {
|
||||
set(r, c, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
B_HE_PRED => {
|
||||
let rows = [avg3(p, l[0], l[1]), avg3(l[0], l[1], l[2]), avg3(l[1], l[2], l[3]), avg3(l[2], l[3], l[3])];
|
||||
for (r, v) in rows.into_iter().enumerate() {
|
||||
for c in 0..4 {
|
||||
set(r, c, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
B_LD_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
let d = r + c;
|
||||
set(r, c, if d == 6 { avg3(a[6], a[7], a[7]) } else { a3(d as i32 + 1) });
|
||||
}
|
||||
}
|
||||
}
|
||||
B_RD_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
// (3,0) is E+1 ... (0,3) is E+7.
|
||||
set(r, c, e3(4 + c - r));
|
||||
}
|
||||
}
|
||||
}
|
||||
B_VR_PRED => {
|
||||
set(3, 0, e3(2));
|
||||
set(2, 0, e3(3));
|
||||
let v = e3(4);
|
||||
set(3, 1, v);
|
||||
set(1, 0, v);
|
||||
let v = e2(4);
|
||||
set(2, 1, v);
|
||||
set(0, 0, v);
|
||||
let v = e3(5);
|
||||
set(3, 2, v);
|
||||
set(1, 1, v);
|
||||
let v = e2(5);
|
||||
set(2, 2, v);
|
||||
set(0, 1, v);
|
||||
let v = e3(6);
|
||||
set(3, 3, v);
|
||||
set(1, 2, v);
|
||||
let v = e2(6);
|
||||
set(2, 3, v);
|
||||
set(0, 2, v);
|
||||
set(1, 3, e3(7));
|
||||
set(0, 3, e2(7));
|
||||
}
|
||||
B_VL_PRED => {
|
||||
let a2 = |j: usize| avg2(a[j], a[j + 1]);
|
||||
set(0, 0, a2(0));
|
||||
set(1, 0, a3(1));
|
||||
let v = a2(1);
|
||||
set(2, 0, v);
|
||||
set(0, 1, v);
|
||||
let v = a3(2);
|
||||
set(1, 1, v);
|
||||
set(3, 0, v);
|
||||
let v = a2(2);
|
||||
set(2, 1, v);
|
||||
set(0, 2, v);
|
||||
let v = a3(3);
|
||||
set(3, 1, v);
|
||||
set(1, 2, v);
|
||||
let v = a2(3);
|
||||
set(2, 2, v);
|
||||
set(0, 3, v);
|
||||
let v = a3(4);
|
||||
set(3, 2, v);
|
||||
set(1, 3, v);
|
||||
set(2, 3, a3(5));
|
||||
set(3, 3, a3(6));
|
||||
}
|
||||
B_HD_PRED => {
|
||||
set(3, 0, e2(0));
|
||||
set(3, 1, e3(1));
|
||||
let v = e2(1);
|
||||
set(2, 0, v);
|
||||
set(3, 2, v);
|
||||
let v = e3(2);
|
||||
set(2, 1, v);
|
||||
set(3, 3, v);
|
||||
let v = e2(2);
|
||||
set(2, 2, v);
|
||||
set(1, 0, v);
|
||||
let v = e3(3);
|
||||
set(2, 3, v);
|
||||
set(1, 1, v);
|
||||
let v = e2(3);
|
||||
set(1, 2, v);
|
||||
set(0, 0, v);
|
||||
let v = e3(4);
|
||||
set(1, 3, v);
|
||||
set(0, 1, v);
|
||||
set(0, 2, e3(5));
|
||||
set(0, 3, e3(6));
|
||||
}
|
||||
B_HU_PRED => {
|
||||
set(0, 0, avg2(l[0], l[1]));
|
||||
set(0, 1, avg3(l[0], l[1], l[2]));
|
||||
let v = avg2(l[1], l[2]);
|
||||
set(0, 2, v);
|
||||
set(1, 0, v);
|
||||
let v = avg3(l[1], l[2], l[3]);
|
||||
set(0, 3, v);
|
||||
set(1, 1, v);
|
||||
let v = avg2(l[2], l[3]);
|
||||
set(1, 2, v);
|
||||
set(2, 0, v);
|
||||
let v = avg3(l[2], l[3], l[3]);
|
||||
set(1, 3, v);
|
||||
set(2, 1, v);
|
||||
for (r, c) in [(2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3)] {
|
||||
set(r, c, l[3]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// B_DC_PRED
|
||||
let sum: u32 = a[..4].iter().chain(l.iter()).map(|&v| u32::from(v)).sum();
|
||||
b.fill(((sum + 4) >> 3) as u8);
|
||||
}
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// V, TM and DC predictions of a whole block, and DC with an edge missing.
|
||||
#[test]
|
||||
fn whole_block_modes() {
|
||||
let e = Edges::<8> { above: [10, 20, 30, 40, 50, 60, 70, 80], left: [5; 8], corner: 10, have_above: true, have_left: true };
|
||||
let mut out = [0u8; 64];
|
||||
predict_block(V_PRED, &e, &mut out);
|
||||
assert_eq!(&out[56..64], &e.above);
|
||||
predict_block(TM_PRED, &e, &mut out);
|
||||
assert_eq!(out[0], 5, "5 + 10 - 10");
|
||||
assert_eq!(out[7], 75);
|
||||
predict_block(DC_PRED, &e, &mut out);
|
||||
assert_eq!(out[0], ((360 + 40 + 8) >> 4) as u8);
|
||||
let top = Edges::<8> { have_above: false, ..e };
|
||||
predict_block(DC_PRED, &top, &mut out);
|
||||
assert_eq!(out[0], 5, "left only");
|
||||
}
|
||||
|
||||
/// Each subblock mode follows the direction of its lines.
|
||||
#[test]
|
||||
fn subblock_modes_follow_their_lines() {
|
||||
let a = [10, 20, 30, 40, 50, 60, 70, 80];
|
||||
let l = [15, 25, 35, 45];
|
||||
let p = 5;
|
||||
let ld = predict_sub(B_LD_PRED, &a, &l, p);
|
||||
// Each antidiagonal is one value.
|
||||
assert_eq!(ld[1], ld[4]);
|
||||
assert_eq!(ld[3], ld[12]);
|
||||
assert_eq!(ld[15], avg3(70, 80, 80));
|
||||
let rd = predict_sub(B_RD_PRED, &a, &l, p);
|
||||
assert_eq!(rd[0], rd[5]);
|
||||
assert_eq!(rd[0], avg3(15, 5, 10));
|
||||
let hu = predict_sub(B_HU_PRED, &a, &l, p);
|
||||
assert_eq!(hu[15], 45);
|
||||
let dc = predict_sub(B_DC_PRED, &a, &l, p);
|
||||
assert_eq!(dc[0], ((100 + 120 + 4) >> 3) as u8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
//! Constant tables of the VP8 key-frame syntax, as RFC 6386 (November 2011) gives them.
|
||||
//!
|
||||
//! The multi-dimensional tables are generated from the RFC's own text (sections 11.5, 13.4,
|
||||
//! 13.5 and 14.1) with its comments taken out, not typed.
|
||||
|
||||
/// Probabilities of the key-frame luma mode tree (§11.2).
|
||||
pub(crate) const KF_YMODE_PROB: [u8; 4] = [145, 156, 163, 128];
|
||||
|
||||
/// Probabilities of the key-frame chroma mode tree (§11.4).
|
||||
pub(crate) const KF_UV_MODE_PROB: [u8; 3] = [142, 114, 183];
|
||||
|
||||
/// Subblock mode probabilities by the modes above and to the left (§11.5).
|
||||
pub(crate) const KF_BMODE_PROB: [[[u8; 9]; 10]; 10] = [
|
||||
[
|
||||
[231, 120, 48, 89, 115, 113, 120, 152, 112],
|
||||
[152, 179, 64, 126, 170, 118, 46, 70, 95],
|
||||
[175, 69, 143, 80, 85, 82, 72, 155, 103],
|
||||
[56, 58, 10, 171, 218, 189, 17, 13, 152],
|
||||
[144, 71, 10, 38, 171, 213, 144, 34, 26],
|
||||
[114, 26, 17, 163, 44, 195, 21, 10, 173],
|
||||
[121, 24, 80, 195, 26, 62, 44, 64, 85],
|
||||
[170, 46, 55, 19, 136, 160, 33, 206, 71],
|
||||
[63, 20, 8, 114, 114, 208, 12, 9, 226],
|
||||
[81, 40, 11, 96, 182, 84, 29, 16, 36],
|
||||
],
|
||||
[
|
||||
[134, 183, 89, 137, 98, 101, 106, 165, 148],
|
||||
[72, 187, 100, 130, 157, 111, 32, 75, 80],
|
||||
[66, 102, 167, 99, 74, 62, 40, 234, 128],
|
||||
[41, 53, 9, 178, 241, 141, 26, 8, 107],
|
||||
[104, 79, 12, 27, 217, 255, 87, 17, 7],
|
||||
[74, 43, 26, 146, 73, 166, 49, 23, 157],
|
||||
[65, 38, 105, 160, 51, 52, 31, 115, 128],
|
||||
[87, 68, 71, 44, 114, 51, 15, 186, 23],
|
||||
[47, 41, 14, 110, 182, 183, 21, 17, 194],
|
||||
[66, 45, 25, 102, 197, 189, 23, 18, 22],
|
||||
],
|
||||
[
|
||||
[88, 88, 147, 150, 42, 46, 45, 196, 205],
|
||||
[43, 97, 183, 117, 85, 38, 35, 179, 61],
|
||||
[39, 53, 200, 87, 26, 21, 43, 232, 171],
|
||||
[56, 34, 51, 104, 114, 102, 29, 93, 77],
|
||||
[107, 54, 32, 26, 51, 1, 81, 43, 31],
|
||||
[39, 28, 85, 171, 58, 165, 90, 98, 64],
|
||||
[34, 22, 116, 206, 23, 34, 43, 166, 73],
|
||||
[68, 25, 106, 22, 64, 171, 36, 225, 114],
|
||||
[34, 19, 21, 102, 132, 188, 16, 76, 124],
|
||||
[62, 18, 78, 95, 85, 57, 50, 48, 51],
|
||||
],
|
||||
[
|
||||
[193, 101, 35, 159, 215, 111, 89, 46, 111],
|
||||
[60, 148, 31, 172, 219, 228, 21, 18, 111],
|
||||
[112, 113, 77, 85, 179, 255, 38, 120, 114],
|
||||
[40, 42, 1, 196, 245, 209, 10, 25, 109],
|
||||
[100, 80, 8, 43, 154, 1, 51, 26, 71],
|
||||
[88, 43, 29, 140, 166, 213, 37, 43, 154],
|
||||
[61, 63, 30, 155, 67, 45, 68, 1, 209],
|
||||
[142, 78, 78, 16, 255, 128, 34, 197, 171],
|
||||
[41, 40, 5, 102, 211, 183, 4, 1, 221],
|
||||
[51, 50, 17, 168, 209, 192, 23, 25, 82],
|
||||
],
|
||||
[
|
||||
[125, 98, 42, 88, 104, 85, 117, 175, 82],
|
||||
[95, 84, 53, 89, 128, 100, 113, 101, 45],
|
||||
[75, 79, 123, 47, 51, 128, 81, 171, 1],
|
||||
[57, 17, 5, 71, 102, 57, 53, 41, 49],
|
||||
[115, 21, 2, 10, 102, 255, 166, 23, 6],
|
||||
[38, 33, 13, 121, 57, 73, 26, 1, 85],
|
||||
[41, 10, 67, 138, 77, 110, 90, 47, 114],
|
||||
[101, 29, 16, 10, 85, 128, 101, 196, 26],
|
||||
[57, 18, 10, 102, 102, 213, 34, 20, 43],
|
||||
[117, 20, 15, 36, 163, 128, 68, 1, 26],
|
||||
],
|
||||
[
|
||||
[138, 31, 36, 171, 27, 166, 38, 44, 229],
|
||||
[67, 87, 58, 169, 82, 115, 26, 59, 179],
|
||||
[63, 59, 90, 180, 59, 166, 93, 73, 154],
|
||||
[40, 40, 21, 116, 143, 209, 34, 39, 175],
|
||||
[57, 46, 22, 24, 128, 1, 54, 17, 37],
|
||||
[47, 15, 16, 183, 34, 223, 49, 45, 183],
|
||||
[46, 17, 33, 183, 6, 98, 15, 32, 183],
|
||||
[65, 32, 73, 115, 28, 128, 23, 128, 205],
|
||||
[40, 3, 9, 115, 51, 192, 18, 6, 223],
|
||||
[87, 37, 9, 115, 59, 77, 64, 21, 47],
|
||||
],
|
||||
[
|
||||
[104, 55, 44, 218, 9, 54, 53, 130, 226],
|
||||
[64, 90, 70, 205, 40, 41, 23, 26, 57],
|
||||
[54, 57, 112, 184, 5, 41, 38, 166, 213],
|
||||
[30, 34, 26, 133, 152, 116, 10, 32, 134],
|
||||
[75, 32, 12, 51, 192, 255, 160, 43, 51],
|
||||
[39, 19, 53, 221, 26, 114, 32, 73, 255],
|
||||
[31, 9, 65, 234, 2, 15, 1, 118, 73],
|
||||
[88, 31, 35, 67, 102, 85, 55, 186, 85],
|
||||
[56, 21, 23, 111, 59, 205, 45, 37, 192],
|
||||
[55, 38, 70, 124, 73, 102, 1, 34, 98],
|
||||
],
|
||||
[
|
||||
[102, 61, 71, 37, 34, 53, 31, 243, 192],
|
||||
[69, 60, 71, 38, 73, 119, 28, 222, 37],
|
||||
[68, 45, 128, 34, 1, 47, 11, 245, 171],
|
||||
[62, 17, 19, 70, 146, 85, 55, 62, 70],
|
||||
[75, 15, 9, 9, 64, 255, 184, 119, 16],
|
||||
[37, 43, 37, 154, 100, 163, 85, 160, 1],
|
||||
[63, 9, 92, 136, 28, 64, 32, 201, 85],
|
||||
[86, 6, 28, 5, 64, 255, 25, 248, 1],
|
||||
[56, 8, 17, 132, 137, 255, 55, 116, 128],
|
||||
[58, 15, 20, 82, 135, 57, 26, 121, 40],
|
||||
],
|
||||
[
|
||||
[164, 50, 31, 137, 154, 133, 25, 35, 218],
|
||||
[51, 103, 44, 131, 131, 123, 31, 6, 158],
|
||||
[86, 40, 64, 135, 148, 224, 45, 183, 128],
|
||||
[22, 26, 17, 131, 240, 154, 14, 1, 209],
|
||||
[83, 12, 13, 54, 192, 255, 68, 47, 28],
|
||||
[45, 16, 21, 91, 64, 222, 7, 1, 197],
|
||||
[56, 21, 39, 155, 60, 138, 23, 102, 213],
|
||||
[85, 26, 85, 85, 128, 128, 32, 146, 171],
|
||||
[18, 11, 7, 63, 144, 171, 4, 4, 246],
|
||||
[35, 27, 10, 146, 174, 171, 12, 26, 128],
|
||||
],
|
||||
[
|
||||
[190, 80, 35, 99, 180, 80, 126, 54, 45],
|
||||
[85, 126, 47, 87, 176, 51, 41, 20, 32],
|
||||
[101, 75, 128, 139, 118, 146, 116, 128, 85],
|
||||
[56, 41, 15, 176, 236, 85, 37, 9, 62],
|
||||
[146, 36, 19, 30, 171, 255, 97, 27, 20],
|
||||
[71, 30, 17, 119, 118, 255, 17, 18, 138],
|
||||
[101, 38, 60, 138, 55, 70, 43, 26, 142],
|
||||
[138, 45, 61, 62, 219, 1, 81, 188, 64],
|
||||
[32, 41, 20, 117, 151, 142, 20, 21, 163],
|
||||
[112, 19, 12, 61, 195, 128, 48, 4, 24],
|
||||
],
|
||||
];
|
||||
|
||||
/// Probabilities of the flags that say a coefficient probability is updated (§13.4).
|
||||
pub(crate) const COEFF_UPDATE_PROBS: [[[[u8; 11]; 3]; 8]; 4] = [
|
||||
[
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255],
|
||||
[250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255],
|
||||
[234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255],
|
||||
[251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255],
|
||||
[248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
[
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/// Coefficient token probabilities at the start of every key frame (§13.5).
|
||||
pub(crate) const DEFAULT_COEFF_PROBS: [[[[u8; 11]; 3]; 8]; 4] = [
|
||||
[
|
||||
[
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128],
|
||||
[189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128],
|
||||
[106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128],
|
||||
[181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128],
|
||||
[78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128],
|
||||
[184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128],
|
||||
[77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128],
|
||||
[170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128],
|
||||
[37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128],
|
||||
[207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128],
|
||||
[102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128],
|
||||
[177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128],
|
||||
[80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62],
|
||||
[131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1],
|
||||
[68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128],
|
||||
],
|
||||
[
|
||||
[1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128],
|
||||
[184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128],
|
||||
[81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128],
|
||||
],
|
||||
[
|
||||
[1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128],
|
||||
[99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128],
|
||||
[23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128],
|
||||
],
|
||||
[
|
||||
[1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128],
|
||||
[109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128],
|
||||
[44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128],
|
||||
[94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128],
|
||||
[22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128],
|
||||
[124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128],
|
||||
[35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128],
|
||||
[121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128],
|
||||
[45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128],
|
||||
[203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
[137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128],
|
||||
[175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128],
|
||||
[73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128],
|
||||
],
|
||||
[
|
||||
[1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128],
|
||||
[239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128],
|
||||
[155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128],
|
||||
[201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128],
|
||||
[69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
[223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128],
|
||||
[141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
[190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128],
|
||||
[149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
[213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128],
|
||||
[55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255],
|
||||
[126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128],
|
||||
[61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128],
|
||||
],
|
||||
[
|
||||
[1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128],
|
||||
[166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128],
|
||||
[39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128],
|
||||
],
|
||||
[
|
||||
[1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128],
|
||||
[124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128],
|
||||
[24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128],
|
||||
],
|
||||
[
|
||||
[1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128],
|
||||
[149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128],
|
||||
[28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128],
|
||||
],
|
||||
[
|
||||
[1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128],
|
||||
[123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128],
|
||||
[20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128],
|
||||
[168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128],
|
||||
[47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128],
|
||||
[141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128],
|
||||
[42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128],
|
||||
],
|
||||
[
|
||||
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
[238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/// Coefficient position to probability band (§13.3).
|
||||
pub(crate) const COEFF_BANDS: [usize; 16] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7];
|
||||
|
||||
/// Extra-bit probabilities of the six value categories, high bit first (§13.2).
|
||||
pub(crate) const PCAT: [&[u8]; 6] = [
|
||||
&[159],
|
||||
&[165, 145],
|
||||
&[173, 148, 140],
|
||||
&[176, 155, 140, 135],
|
||||
&[180, 157, 141, 134, 130],
|
||||
&[254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129],
|
||||
];
|
||||
|
||||
/// Smallest value of each category (§13.2).
|
||||
pub(crate) const CAT_BASE: [u32; 6] = [5, 7, 11, 19, 35, 67];
|
||||
|
||||
/// DC dequantization factor by quantizer index (§14.1).
|
||||
pub(crate) const DC_QLOOKUP: [i32; 128] = [
|
||||
4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
||||
35, 36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
|
||||
69, 70, 71, 72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110,
|
||||
112, 114, 116, 118, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157,
|
||||
];
|
||||
|
||||
/// AC dequantization factor by quantizer index (§14.1).
|
||||
pub(crate) const AC_QLOOKUP: [i32; 128] = [
|
||||
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
|
||||
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96,
|
||||
98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173,
|
||||
177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284,
|
||||
];
|
||||
@@ -0,0 +1,157 @@
|
||||
//! The 4×4 transforms: the decoder's inverse DCT and WHT exactly as RFC 6386 §14.3–14.4 gives
|
||||
//! them, and forward transforms derived from them.
|
||||
//!
|
||||
//! The inverse DCT is, apart from its integer rounding, `R = T·X·Tᵀ / 8` with the 1-D basis
|
||||
//! `T = [[1, C, 1, S], [1, S, −1, −C], [1, −S, −1, C], [1, −C, 1, −S]]` (C = √2·cos π/8,
|
||||
//! S = √2·sin π/8). T's columns are orthogonal and of norm 2, so `TᵀT = 4I` and the exact inverse
|
||||
//! is `X = Tᵀ·R·T / 2`. The inverse WHT is likewise `R = H·Y·Hᵀ / 8` with the 4×4 Hadamard
|
||||
//! matrix H in the order of §14.3, so `Y = Hᵀ·R·H / 2`.
|
||||
|
||||
const COSPI8SQRT2MINUS1: i32 = 20091;
|
||||
const SINPI8SQRT2: i32 = 35468;
|
||||
|
||||
/// Inverse DCT of dequantized coefficients (raster order) to residue, §14.4. Intermediate and
|
||||
/// final values are 16-bit, as the RFC stores them.
|
||||
pub(crate) fn idct(input: &[i16; 16]) -> [i16; 16] {
|
||||
let mut tmp = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let x = |r: usize| i32::from(input[r * 4 + i]);
|
||||
let a1 = x(0) + x(2);
|
||||
let b1 = x(0) - x(2);
|
||||
let c1 = ((x(1) * SINPI8SQRT2) >> 16) - (x(3) + ((x(3) * COSPI8SQRT2MINUS1) >> 16));
|
||||
let d1 = (x(1) + ((x(1) * COSPI8SQRT2MINUS1) >> 16)) + ((x(3) * SINPI8SQRT2) >> 16);
|
||||
tmp[i] = (a1 + d1) as i16;
|
||||
tmp[12 + i] = (a1 - d1) as i16;
|
||||
tmp[4 + i] = (b1 + c1) as i16;
|
||||
tmp[8 + i] = (b1 - c1) as i16;
|
||||
}
|
||||
let mut out = [0i16; 16];
|
||||
for r in 0..4 {
|
||||
let x = |c: usize| i32::from(tmp[r * 4 + c]);
|
||||
let a1 = x(0) + x(2);
|
||||
let b1 = x(0) - x(2);
|
||||
let c1 = ((x(1) * SINPI8SQRT2) >> 16) - (x(3) + ((x(3) * COSPI8SQRT2MINUS1) >> 16));
|
||||
let d1 = (x(1) + ((x(1) * COSPI8SQRT2MINUS1) >> 16)) + ((x(3) * SINPI8SQRT2) >> 16);
|
||||
out[r * 4] = ((a1 + d1 + 4) >> 3) as i16;
|
||||
out[r * 4 + 3] = ((a1 - d1 + 4) >> 3) as i16;
|
||||
out[r * 4 + 1] = ((b1 + c1 + 4) >> 3) as i16;
|
||||
out[r * 4 + 2] = ((b1 - c1 + 4) >> 3) as i16;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Inverse WHT of the dequantized Y2 block to the 16 luma DC coefficients, §14.3.
|
||||
pub(crate) fn iwht(input: &[i16; 16]) -> [i16; 16] {
|
||||
let mut tmp = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let x = |r: usize| i32::from(input[r * 4 + i]);
|
||||
let a1 = x(0) + x(3);
|
||||
let b1 = x(1) + x(2);
|
||||
let c1 = x(1) - x(2);
|
||||
let d1 = x(0) - x(3);
|
||||
tmp[i] = (a1 + b1) as i16;
|
||||
tmp[4 + i] = (c1 + d1) as i16;
|
||||
tmp[8 + i] = (a1 - b1) as i16;
|
||||
tmp[12 + i] = (d1 - c1) as i16;
|
||||
}
|
||||
let mut out = [0i16; 16];
|
||||
for r in 0..4 {
|
||||
let x = |c: usize| i32::from(tmp[r * 4 + c]);
|
||||
let a1 = x(0) + x(3);
|
||||
let b1 = x(1) + x(2);
|
||||
let c1 = x(1) - x(2);
|
||||
let d1 = x(0) - x(3);
|
||||
out[r * 4] = ((a1 + b1 + 3) >> 3) as i16;
|
||||
out[r * 4 + 1] = ((c1 + d1 + 3) >> 3) as i16;
|
||||
out[r * 4 + 2] = ((a1 - b1 + 3) >> 3) as i16;
|
||||
out[r * 4 + 3] = ((d1 - c1 + 3) >> 3) as i16;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The forward DCT's basis, scaled to match the spec's inverse.
|
||||
fn basis_dct() -> [[f64; 4]; 4] {
|
||||
let c = std::f64::consts::SQRT_2 * (std::f64::consts::PI / 8.0).cos();
|
||||
let s = std::f64::consts::SQRT_2 * (std::f64::consts::PI / 8.0).sin();
|
||||
[[1.0, c, 1.0, s], [1.0, s, -1.0, -c], [1.0, -s, -1.0, c], [1.0, -c, 1.0, -s]]
|
||||
}
|
||||
|
||||
const HADAMARD: [[f64; 4]; 4] = [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, -1.0, -1.0], [1.0, -1.0, -1.0, 1.0], [1.0, -1.0, 1.0, -1.0]];
|
||||
|
||||
/// `Mᵀ·R·M / 2` for a 4×4 `R` in raster order.
|
||||
fn forward(m: &[[f64; 4]; 4], r: &[f64; 16]) -> [f64; 16] {
|
||||
let mut tmp = [0f64; 16]; // Mᵀ·R
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
tmp[i * 4 + j] = (0..4).map(|k| m[k][i] * r[k * 4 + j]).sum();
|
||||
}
|
||||
}
|
||||
let mut out = [0f64; 16];
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
out[i * 4 + j] = (0..4).map(|k| tmp[i * 4 + k] * m[k][j]).sum::<f64>() / 2.0;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Forward DCT of a 4×4 residue (raster order), in the scale [`idct`] inverts.
|
||||
pub(crate) fn fdct(residue: &[i32; 16]) -> [f64; 16] {
|
||||
let r: [f64; 16] = std::array::from_fn(|i| f64::from(residue[i]));
|
||||
forward(&basis_dct(), &r)
|
||||
}
|
||||
|
||||
/// Forward WHT of the 16 luma DC coefficients (raster order of their subblocks), in the scale
|
||||
/// [`iwht`] inverts.
|
||||
pub(crate) fn fwht(dc: &[f64; 16]) -> [f64; 16] {
|
||||
forward(&HADAMARD, dc)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The DCT and its inverse round-trip residuals within a level.
|
||||
#[test]
|
||||
fn dct_round_trips() {
|
||||
let mut x = 7u32;
|
||||
for _ in 0..500 {
|
||||
let r: [i32; 16] = std::array::from_fn(|_| {
|
||||
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||
(x >> 16) as i32 % 511 - 255
|
||||
});
|
||||
let c = fdct(&r);
|
||||
let q: [i16; 16] = std::array::from_fn(|i| c[i].round() as i16);
|
||||
let back = idct(&q);
|
||||
for i in 0..16 {
|
||||
assert!((i32::from(back[i]) - r[i]).abs() <= 1, "{r:?} -> {back:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Walsh–Hadamard transform and its inverse round-trip within a level.
|
||||
#[test]
|
||||
fn wht_round_trips() {
|
||||
let mut x = 99u32;
|
||||
for _ in 0..500 {
|
||||
let d: [f64; 16] = std::array::from_fn(|_| {
|
||||
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||
f64::from((x >> 16) as i32 % 4001 - 2000)
|
||||
});
|
||||
let y = fwht(&d);
|
||||
let q: [i16; 16] = std::array::from_fn(|i| y[i].round() as i16);
|
||||
let back = iwht(&q);
|
||||
for i in 0..16 {
|
||||
assert!((f64::from(back[i]) - d[i]).abs() <= 1.0, "{d:?} -> {back:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A lone DC coefficient inverts to a flat block.
|
||||
#[test]
|
||||
fn a_lone_dc_is_flat() {
|
||||
let mut c = [0i16; 16];
|
||||
c[0] = 80;
|
||||
assert!(idct(&c).iter().all(|&v| v == (80 + 4) >> 3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//! RGBA to Y′CbCr 4:2:0 (Recommendation BT.601, studio range), padded to whole macroblocks.
|
||||
//!
|
||||
//! RFC 9649 §2.5 asks for BT.601 between RGB and the VP8 planes. Chroma is the average of each
|
||||
//! 2×2 group weighted by alpha, so colour hidden under full transparency does not tint the edge of
|
||||
//! what shows; a group with no alpha at all averages evenly.
|
||||
|
||||
use super::frame::Plane;
|
||||
|
||||
/// BT.601 studio-range luma (16–235) from RGB.
|
||||
fn luma(r: i32, g: i32, b: i32) -> u8 {
|
||||
(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16).clamp(0, 255) as u8
|
||||
}
|
||||
|
||||
/// BT.601 studio-range Cb and Cr (16–240) from RGB.
|
||||
fn chroma(r: i32, g: i32, b: i32) -> (u8, u8) {
|
||||
let u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
|
||||
let v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
|
||||
(u.clamp(0, 255) as u8, v.clamp(0, 255) as u8)
|
||||
}
|
||||
|
||||
/// The planes at the coded size: whole macroblocks, the picture's last row and column repeated
|
||||
/// into the padding. The caller has checked `rgba` holds `width × height` pixels.
|
||||
pub(crate) fn rgba_to_yuv420(rgba: &[u8], width: u32, height: u32) -> (Plane, Plane, Plane) {
|
||||
let (w, h) = (width as usize, height as usize);
|
||||
let (mbw, mbh) = (w.div_ceil(16), h.div_ceil(16));
|
||||
let mut y = Plane::new(mbw * 16, mbh * 16);
|
||||
let mut u = Plane::new(mbw * 8, mbh * 8);
|
||||
let mut v = Plane::new(mbw * 8, mbh * 8);
|
||||
let px = |x: usize, yy: usize| -> [i32; 4] {
|
||||
let i = (yy.min(h - 1) * w + x.min(w - 1)) * 4;
|
||||
match rgba.get(i..i + 4) {
|
||||
Some(p) => [i32::from(p[0]), i32::from(p[1]), i32::from(p[2]), i32::from(p[3])],
|
||||
None => [0; 4],
|
||||
}
|
||||
};
|
||||
for yy in 0..y.height {
|
||||
for x in 0..y.width {
|
||||
let [r, g, b, _] = px(x, yy);
|
||||
y.set(x, yy, luma(r, g, b));
|
||||
}
|
||||
}
|
||||
for cy in 0..u.height {
|
||||
for cx in 0..u.width {
|
||||
let group = [px(2 * cx, 2 * cy), px(2 * cx + 1, 2 * cy), px(2 * cx, 2 * cy + 1), px(2 * cx + 1, 2 * cy + 1)];
|
||||
let weight: i32 = group.iter().map(|p| p[3]).sum();
|
||||
let (mut r, mut g, mut b) = (0, 0, 0);
|
||||
for p in &group {
|
||||
let k = if weight > 0 { p[3] } else { 1 };
|
||||
r += p[0] * k;
|
||||
g += p[1] * k;
|
||||
b += p[2] * k;
|
||||
}
|
||||
let total = if weight > 0 { weight } else { 4 };
|
||||
let (cu, cv) = chroma((r + total / 2) / total, (g + total / 2) / total, (b + total / 2) / total);
|
||||
u.set(cx, cy, cu);
|
||||
v.set(cx, cy, cv);
|
||||
}
|
||||
}
|
||||
(y, u, v)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Black and white map to 16 and 235, and grey has neutral chroma.
|
||||
#[test]
|
||||
fn studio_range() {
|
||||
assert_eq!(luma(0, 0, 0), 16);
|
||||
assert_eq!(luma(255, 255, 255), 235);
|
||||
assert_eq!(chroma(128, 128, 128), (128, 128));
|
||||
}
|
||||
|
||||
/// Padding repeats the edge pixel, and chroma ignores fully transparent pixels.
|
||||
#[test]
|
||||
fn padding_repeats_the_edge_and_alpha_weights_chroma() {
|
||||
// 3×1: red opaque, then two fully transparent black pixels.
|
||||
let rgba = [255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
let (y, u, v) = rgba_to_yuv420(&rgba, 3, 1);
|
||||
assert_eq!((y.width, y.height, u.width), (16, 16, 8));
|
||||
assert_eq!(y.get(15, 15), y.get(2, 0), "the last pixel repeats");
|
||||
// The first chroma sample sees only the red pixel's colour.
|
||||
assert_eq!((u.get(0, 0), v.get(0, 0)), chroma(255, 0, 0));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
//! Clean-room pure-Rust VP8 decoder, implemented from RFC 6386 ("VP8 Data Format and Decoding
|
||||
//! Guide"), sections 1-19.
|
||||
//! Clean-room pure-Rust VP8, implemented from RFC 6386 ("VP8 Data Format and Decoding Guide").
|
||||
//!
|
||||
//! | Feature | Module | What |
|
||||
//! |---|---|---|
|
||||
//! | `vp8-decode` | this module | Decoder: sections 1-19, key and inter frames ([`Decoder`]) |
|
||||
//! | `vp8-encode` | this module | Encoder: key frames, as the lossy picture of a WebP image ([`encode_rgba`]) |
|
||||
//!
|
||||
//! Feature `vp8` turns on both. The decoder:
|
||||
//!
|
||||
//! Supported: the whole format — key frames and inter frames, all intra modes (16x16, chroma,
|
||||
//! the ten 4x4 subblock modes), inter prediction from the last, golden and altref frames with
|
||||
@@ -18,20 +24,32 @@
|
||||
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod boolcoder;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod decoder;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod error;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod header;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod loopfilter;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod predict;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
#[rustfmt::skip]
|
||||
mod spec_tables;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod tables;
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
mod transform;
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
pub use decoder::{DecodeStats, Decoder};
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
pub use error::{Error, Result};
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
/// A decoded picture: 8-bit 4:2:0, planes tight (stride = width).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Picture {
|
||||
@@ -49,6 +67,7 @@ pub struct Picture {
|
||||
pub color_space: u8,
|
||||
}
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
/// Stream parameters read from a key frame's uncompressed chunk (see [`keyframe_info`]).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct KeyframeInfo {
|
||||
@@ -60,6 +79,7 @@ pub struct KeyframeInfo {
|
||||
pub version: u8,
|
||||
}
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
/// If `frame` is a key frame (a random access point), its parameters.
|
||||
pub fn keyframe_info(frame: &[u8]) -> Option<KeyframeInfo> {
|
||||
let tag = header::FrameTag::parse(frame).ok()?;
|
||||
@@ -72,7 +92,13 @@ pub fn keyframe_info(frame: &[u8]) -> Option<KeyframeInfo> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "vp8-decode")]
|
||||
/// Whether `frame` is a key frame (decoding can start there).
|
||||
pub fn is_keyframe(frame: &[u8]) -> bool {
|
||||
keyframe_info(frame).is_some()
|
||||
}
|
||||
|
||||
#[cfg(feature = "vp8-encode")]
|
||||
mod encode;
|
||||
#[cfg(feature = "vp8-encode")]
|
||||
pub use encode::*;
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//! The VP8 encoder's frames read back by this crate's own decoder: the decode must reproduce the
|
||||
//! encoder's reconstruction exactly (loop filter off) and read as a picture close to the source.
|
||||
|
||||
use craft_video::vp8::{Decoder, Picture, Vp8Frame, Vp8Params, encode_rgba};
|
||||
|
||||
/// A test picture with something for every mode: flat areas, gradients, hard edges, diagonal
|
||||
/// stripes, fine noise, colour.
|
||||
fn picture(w: u32, h: u32, seed: u32) -> Vec<u8> {
|
||||
let mut x = seed.wrapping_mul(2654435761) | 1;
|
||||
let mut out = Vec::with_capacity((w * h * 4) as usize);
|
||||
for py in 0..h {
|
||||
for px in 0..w {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
let noise = (x % 64) as i32 - 32;
|
||||
let (fx, fy) = (px as f32 / w as f32, py as f32 / h as f32);
|
||||
let (r, g, b) = if fx < 0.25 {
|
||||
(200, 40, 40) // flat
|
||||
} else if fx < 0.5 {
|
||||
((fx * 400.0) as i32, (fy * 255.0) as i32, 128) // gradient
|
||||
} else if fx < 0.75 {
|
||||
let stripe = ((px + py) / 3) % 2 == 0; // diagonal stripes
|
||||
if stripe { (240, 240, 240) } else { (20, 30, 90) }
|
||||
} else {
|
||||
(128 + noise * 2, 100 + noise, 160 - noise) // noise
|
||||
};
|
||||
let c = |v: i32| v.clamp(0, 255) as u8;
|
||||
out.extend_from_slice(&[c(r), c(g), c(b), 255]);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The decoder's reading of the frame.
|
||||
fn decode(frame: &Vp8Frame) -> Picture {
|
||||
Decoder::new().decode(&frame.data, 0).expect("decode").expect("a key frame is a picture")
|
||||
}
|
||||
|
||||
/// The encoder's reconstruction cropped to the picture, as the decoder's planes: Y, then U, then V.
|
||||
fn reconstruction(frame: &Vp8Frame, w: u32, h: u32) -> Vec<u8> {
|
||||
let (w, h) = (w as usize, h as usize);
|
||||
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
|
||||
let mut out = Vec::with_capacity(w * h + 2 * cw * ch);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
out.push(frame.y.get(x, y));
|
||||
}
|
||||
}
|
||||
for p in [&frame.u, &frame.v] {
|
||||
for y in 0..ch {
|
||||
for x in 0..cw {
|
||||
out.push(p.get(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// With the loop filter off, the decoder reconstructs exactly what the encoder did.
|
||||
#[test]
|
||||
fn the_decoder_reconstructs_exactly_what_the_encoder_did() {
|
||||
for (w, h, quality) in [(64, 48, 90), (33, 17, 50), (100, 75, 10), (16, 16, 100), (130, 66, 75), (1, 1, 60)] {
|
||||
let rgba = picture(w, h, w * 31 + h);
|
||||
let frame = encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: Some(0) }).expect("encode");
|
||||
let name = format!("{w}x{h} q{quality}");
|
||||
let pic = decode(&frame);
|
||||
assert_eq!((pic.width, pic.height), (w, h), "{name}: picture size");
|
||||
let got: Vec<u8> = [&pic.y, &pic.u, &pic.v].into_iter().flatten().copied().collect();
|
||||
let want = reconstruction(&frame, w, h);
|
||||
assert_eq!(got.len(), want.len(), "{name}: plane sizes");
|
||||
if let Some(i) = got.iter().zip(&want).position(|(a, b)| a != b) {
|
||||
panic!("{name}: the decoder differs from the encoder's reconstruction at byte {i} of {} ({} vs {})", got.len(), got[i], want[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The peak signal-to-noise ratio of `b` against `a`, in dB.
|
||||
fn psnr(a: &[u8], b: &[u8]) -> f64 {
|
||||
let se: f64 = a.iter().zip(b).map(|(&x, &y)| (f64::from(x) - f64::from(y)).powi(2)).sum();
|
||||
let mse = se / a.len() as f64;
|
||||
if mse == 0.0 { f64::INFINITY } else { 10.0 * (255.0f64 * 255.0 / mse).log10() }
|
||||
}
|
||||
|
||||
/// A higher quality decodes closer to the quality-100 picture.
|
||||
#[test]
|
||||
fn the_decoder_reads_it_back_close_to_the_source() {
|
||||
// Against the decode at quality 100, so the comparison is of the coding alone and not of
|
||||
// the colour 4:2:0 cannot hold (this picture's fine coloured stripes and noise).
|
||||
let (w, h) = (96, 64);
|
||||
let rgba = picture(w, h, 7);
|
||||
let planes = |quality| {
|
||||
let frame = encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: None }).expect("encode");
|
||||
let pic = decode(&frame);
|
||||
assert_eq!((pic.width, pic.height), (w, h));
|
||||
[&pic.y, &pic.u, &pic.v].into_iter().flatten().copied().collect::<Vec<u8>>()
|
||||
};
|
||||
let best = planes(100);
|
||||
let mut last = 0.0;
|
||||
for quality in [20u8, 60, 95] {
|
||||
let q = psnr(&planes(quality), &best);
|
||||
assert!(q >= last, "quality {quality} is no closer than a lower quality: {q:.1} dB after {last:.1}");
|
||||
last = q;
|
||||
}
|
||||
assert!(last > 38.0, "quality 95: {last:.1} dB");
|
||||
}
|
||||
|
||||
/// A higher quality gives a larger frame.
|
||||
#[test]
|
||||
fn higher_quality_spends_more_bytes() {
|
||||
let (w, h) = (96, 64);
|
||||
let rgba = picture(w, h, 11);
|
||||
let size = |quality| encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: None }).expect("encode").data.len();
|
||||
let (low, mid, high) = (size(10), size(60), size(98));
|
||||
assert!(low < mid && mid < high, "{low} {mid} {high}");
|
||||
}
|
||||
|
||||
/// Empty, short and oversized pictures are errors.
|
||||
#[test]
|
||||
fn bad_sizes_are_errors() {
|
||||
assert!(encode_rgba(&[], 0, 0, &Vp8Params::default()).is_err());
|
||||
assert!(encode_rgba(&[0; 12], 2, 2, &Vp8Params::default()).is_err());
|
||||
assert!(encode_rgba(&[0; 4], 16384, 1, &Vp8Params::default()).is_err());
|
||||
}
|
||||
Reference in New Issue
Block a user