#432: VP8 video decodes (clean-room pure-Rust VP8 decoder)
VP8 WebM showed black video: there was no VP8 decoder. New L0 crate filmcraft-vp8, implemented from RFC 6386 sections 1-19 (tables extracted from the RFC text, nothing from the attached reference decoder): every coding tool, versions 0-3, hidden altref frames, size changes. Bit-exact with libvpx on 62 libvpx VP8 test vectors, real-world FATE samples and generated fixtures; never panics on corrupted input. Wired into codecs for Matroska / WebM (V_VP8, including the alpha layer of transparent WebM) and MP4 (vp08). VP8 colour is BT.601 (RFC 6386 9.2). A hidden altref frame is a WebM block of its own with the time of the frame that follows it: the VP8 time lookup now moves on to that shown frame instead of repeating the previous one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e2d0bc2d3
commit
53a0d959fe
32 files changed
+3388
-11
No files matched your search
Generated
+8
@@ -1836,6 +1836,7 @@ dependencies = [
|
||||
"filmcraft-prores",
|
||||
"filmcraft-testkit",
|
||||
"filmcraft-time",
|
||||
"filmcraft-vp8",
|
||||
"filmcraft-vp9",
|
||||
"image",
|
||||
"log",
|
||||
@@ -2306,6 +2307,13 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filmcraft-vp8"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filmcraft-vp9"
|
||||
version = "0.6.0"
|
||||
|
||||
@@ -26,6 +26,7 @@ filmcraft-ac3 = { path = "crates/ac3" }
|
||||
filmcraft-ogg = { path = "crates/ogg" }
|
||||
filmcraft-h264 = { path = "crates/h264" }
|
||||
filmcraft-hevc = { path = "crates/hevc" }
|
||||
filmcraft-vp8 = { path = "crates/vp8" }
|
||||
filmcraft-vp9 = { path = "crates/vp9" }
|
||||
filmcraft-prores = { path = "crates/prores" }
|
||||
filmcraft-dnx = { path = "crates/dnx" }
|
||||
@@ -125,6 +126,9 @@ opt-level = 3
|
||||
[profile.dev.package.filmcraft-opus]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-vp8]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-vp9]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ No FFmpeg inside. The video codecs, AAC, Opus and the containers are our own Rus
|
||||
| **HEVC / H.265** | ✓ | | Main and Main 10, bit-exact on 41 streams (tiles, WPP, PCM, long-term references), about 225 fps at 1080p |
|
||||
| **Apple ProRes** | ✓ | ✓ | Decodes 422 Proxy to 4444 XQ; export writes 422 HQ |
|
||||
| **AAC-LC** | ✓ | ✓ | |
|
||||
| **VP8** | ✓ | | Every coding tool, bitstream versions 0–3, hidden altref frames, alpha layer of transparent WebM; bit-exact with libvpx on its published VP8 test vectors; in WebM/Matroska and MP4 |
|
||||
| **VP9** | ✓ | | Profiles 0–3, 8/10/12-bit, 4:2:0 to 4:4:4, tiles and superframes; bit-exact on 50+ streams; in WebM/Matroska and MP4 |
|
||||
| **Opus** | ✓ | | SILK, CELT, hybrid and multistream surround; range-exact on every RFC 8251 conformance vector; in WebM/Matroska and MP4 |
|
||||
| **AV1** | ✓ | | Main profile, 8/10-bit 4:2:0 and monochrome, every coding tool incl. film grain, superres and spatial layers; bit-exact with libdav1d on the libaom vectors tested; in MP4 and WebM/Matroska |
|
||||
|
||||
@@ -24,6 +24,7 @@ filmcraft-ogg = { workspace = true }
|
||||
filmcraft-bitstream = { workspace = true }
|
||||
filmcraft-h264 = { workspace = true }
|
||||
filmcraft-hevc = { workspace = true }
|
||||
filmcraft-vp8 = { workspace = true }
|
||||
filmcraft-vp9 = { workspace = true }
|
||||
filmcraft-prores = { workspace = true }
|
||||
filmcraft-dnx = { workspace = true }
|
||||
|
||||
@@ -70,9 +70,10 @@ impl From<CodecError> for filmcraft_media::MediaError {
|
||||
}
|
||||
|
||||
/// Our own (pure-Rust) decoders, in the order they are tried.
|
||||
const BUILTIN_FACTORIES: [VideoDecoderFactory; 9] = [
|
||||
const BUILTIN_FACTORIES: [VideoDecoderFactory; 10] = [
|
||||
video::h264_factory,
|
||||
video::hevc_factory,
|
||||
video::vp8_factory,
|
||||
video::vp9_factory,
|
||||
video::av1_factory,
|
||||
video::apv_factory,
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct MkvSource {
|
||||
/// Video codec as an ISO-BMFF sample entry (for the decoder factories).
|
||||
ventry: Option<SampleEntry>,
|
||||
video: GopCache,
|
||||
/// The alpha layer of a VP9 track with `AlphaMode` 1 (WebM transparency, #402).
|
||||
/// The alpha layer of a VP8 / VP9 track with `AlphaMode` 1 (WebM transparency, #402).
|
||||
alpha: Option<Arc<crate::webm_alpha::AlphaLayer>>,
|
||||
/// The playable audio tracks in file order: `info.audio_streams[k]` describes `audios[k]`.
|
||||
audios: Vec<MkvAudio>,
|
||||
@@ -232,6 +232,7 @@ fn vp9_config(private: &[u8], v: Option<&filmcraft_matroska::VideoInfo>) -> VpcC
|
||||
|
||||
fn sample_entry(c: &Codec, private: &[u8], v: Option<&filmcraft_matroska::VideoInfo>, w: u16, h: u16) -> Option<SampleEntry> {
|
||||
Some(match c {
|
||||
Codec::Vp8 => SampleEntry::video(FourCc(*b"vp08"), CodecConfig::Unknown { fourcc: FourCc(*b"vp08"), raw: Vec::new() }, w, h),
|
||||
Codec::Vp9 { private } => SampleEntry::video(FourCc(*b"vp09"), CodecConfig::Vp9(vp9_config(private, v)), w, h),
|
||||
Codec::Av1 { av1c } => SampleEntry::video(FourCc(*b"av01"), CodecConfig::Av1(filmcraft_isobmff::Av1Config::parse(av1c).unwrap_or_default()), w, h),
|
||||
Codec::Apv { apvc } => SampleEntry::apv(filmcraft_isobmff::ApvConfig::parse(apvc).unwrap_or_default(), w, h),
|
||||
@@ -429,9 +430,11 @@ impl MkvSource {
|
||||
Some(MkvAudio { track: i, state, starts, preroll })
|
||||
})
|
||||
.collect();
|
||||
// WebM transparency: VP9 alpha in each block's BlockAdditional (ID 1)
|
||||
// WebM transparency: VP8 / VP9 alpha in each block's BlockAdditional (ID 1)
|
||||
let alpha = vtrack.and_then(|i| file.tracks.get(i)).filter(|t| {
|
||||
matches!(t.codec, Codec::Vp9 { .. }) && t.video.as_ref().is_some_and(|v| v.alpha_mode != 0) && t.samples.iter().any(|s| s.addition.is_some())
|
||||
matches!(t.codec, Codec::Vp8 | Codec::Vp9 { .. })
|
||||
&& t.video.as_ref().is_some_and(|v| v.alpha_mode != 0)
|
||||
&& t.samples.iter().any(|s| s.addition.is_some())
|
||||
});
|
||||
let alpha = alpha.map(|t| Arc::new(crate::webm_alpha::AlphaLayer::new(bytes.clone(), t.samples.iter().map(|s| (s.pts, s.addition)))));
|
||||
Ok(Self { info, bytes, file, vtrack, ventry, video: GopCache::new(explicit_color).with_rotation(rotation), alpha, audios })
|
||||
@@ -441,6 +444,21 @@ impl MkvSource {
|
||||
self.file.read_sample(&self.bytes, track, i).map_err(|e| CodecError::Container(e.to_string()))
|
||||
}
|
||||
|
||||
/// Whether sample `i` of a VP8 track is a hidden frame (`show_frame` 0, RFC 6386 9.1): only the
|
||||
/// first byte of the frame is read.
|
||||
fn vp8_hidden(&self, track: usize, i: usize) -> bool {
|
||||
let Some(t) = self.file.tracks.get(track) else { return false };
|
||||
let Some(s) = t.samples.get(i) else { return false };
|
||||
let first = match t.frame_prefix(u64::from(s.size)).first() {
|
||||
Some(&b) => Some(b),
|
||||
None => {
|
||||
let mut b = [0u8; 1];
|
||||
(s.size > 0 && self.bytes.0.read_at(s.offset, &mut b).is_ok()).then_some(b[0])
|
||||
}
|
||||
};
|
||||
first.is_some_and(|b| b & 0x10 == 0)
|
||||
}
|
||||
|
||||
fn audio_decoder(&self, c: &Codec, rate: u32) -> crate::Result<PacketDecoder> {
|
||||
use symphonia::core::codecs::{CODEC_TYPE_FLAC, CODEC_TYPE_MP3, CODEC_TYPE_VORBIS};
|
||||
match c {
|
||||
@@ -534,7 +552,20 @@ impl VideoSamples for MkvVideo<'_> {
|
||||
self.src.file.tracks[self.track].sync_sample_before(i)
|
||||
}
|
||||
fn sample_at(&self, t: i64) -> Option<usize> {
|
||||
self.src.file.tracks[self.track].sample_at_pts(t)
|
||||
let track = &self.src.file.tracks[self.track];
|
||||
let i = track.sample_at_pts(t)?;
|
||||
if !matches!(track.codec, Codec::Vp8) {
|
||||
return Some(i);
|
||||
}
|
||||
// VP8 has no superframes: an encoder stores a hidden (altref) frame as a block of its own
|
||||
// with the time of the frame that follows it, which vpxenc moves 1 ms later. The hidden
|
||||
// frame shows nothing, so its time asks for that next shown frame (as decoders that pass
|
||||
// the two on as one packet show it).
|
||||
let mut k = i;
|
||||
while k + 1 < track.samples.len() && k < i + 8 && self.src.vp8_hidden(self.track, k) {
|
||||
k += 1;
|
||||
}
|
||||
Some(k)
|
||||
}
|
||||
fn read(&self, i: usize) -> crate::Result<Vec<u8>> {
|
||||
self.src.read(self.track, i)
|
||||
|
||||
@@ -577,6 +577,7 @@ fn codec_label(c: &CodecConfig) -> String {
|
||||
),
|
||||
CodecConfig::Hevc(_) => "HEVC".into(),
|
||||
CodecConfig::Vp9(c) => format!("VP9 (Profile {})", c.profile),
|
||||
CodecConfig::Unknown { fourcc, .. } if fourcc.0 == *b"vp08" => "VP8".into(),
|
||||
CodecConfig::Av1(c) => format!("AV1 ({} Profile)", ["Main", "High", "Professional"].get(c.seq_profile as usize).unwrap_or(&"Main")),
|
||||
CodecConfig::Apv(a) => match filmcraft_apv::Profile::from_idc(a.profile_idc) {
|
||||
Some(p) => p.name().into(),
|
||||
|
||||
@@ -42,6 +42,8 @@ pub fn from_codec_config(codec: &CodecConfig) -> Option<ColorCodes> {
|
||||
CodecConfig::Avc(c) => avc(c),
|
||||
CodecConfig::Hevc(c) => hevc(c),
|
||||
CodecConfig::Av1(c) => av1(&c.config_obus),
|
||||
// VP8 has no colour signalling: its one colour space is BT.601 YUV, studio swing (RFC 6386 9.2)
|
||||
CodecConfig::Unknown { fourcc, .. } if fourcc.0 == *b"vp08" => Some(ColorCodes { primaries: 2, transfer: 2, matrix: 6, full_range: Some(false) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +449,119 @@ fn vp9_webm_alpha_layer_matches_libvpx() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #432: VP8 in WebM with hidden altref frames (vpxenc, two-pass with ARNR: 11 of its 161
|
||||
/// frames are not shown). Frames requested out of order (seeks back and forth, then sequential
|
||||
/// playback across the hidden frames) are sample-exact against libvpx's decode, and the stream's
|
||||
/// colour is BT.601 (RFC 6386 9.2). Skipped without ffmpeg or vpxenc.
|
||||
#[test]
|
||||
fn vp8_webm_random_access_matches_libvpx() {
|
||||
let frames = 150usize;
|
||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/fixtures/codecs");
|
||||
let path = dir.join("vp8_hidden_altref.webm");
|
||||
if !path.exists() {
|
||||
let Some(y4m) =
|
||||
fixture_path("mandelbrot_352x288_150.y4m", &["-f", "lavfi", "-i", "mandelbrot=s=352x288:r=25", "-frames:v", "150", "-pix_fmt", "yuv420p"])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let tmp = dir.join(format!("vp8_hidden_altref.{}.webm", std::process::id()));
|
||||
let enc = Command::new("vpxenc")
|
||||
.args(["--codec=vp8", "--passes=2", "--auto-alt-ref=1", "--lag-in-frames=25", "--arnr-maxframes=7", "--arnr-strength=5"])
|
||||
.args(["--target-bitrate=300", "--kf-max-dist=150", "-q", "-o"])
|
||||
.arg(&tmp)
|
||||
.arg(&y4m)
|
||||
.status();
|
||||
if !enc.is_ok_and(|s| s.success()) {
|
||||
eprintln!("vpxenc not available; skipping");
|
||||
return;
|
||||
}
|
||||
let _ = std::fs::rename(&tmp, &path);
|
||||
}
|
||||
let p = path.to_string_lossy().into_owned();
|
||||
let Some(reference) =
|
||||
fixture("vp8_hidden_altref.webm.yuv", &["-c:v", "libvpx", "-i", &p, "-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", "yuv420p"])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let bytes: Arc<[u8]> = std::fs::read(&path).unwrap().into();
|
||||
let src = crate::open_bytes("vp8_hidden_altref.webm", bytes).unwrap();
|
||||
let info = src.info().clone();
|
||||
assert_eq!(info.container, "WebM");
|
||||
let v = info.video.as_ref().unwrap();
|
||||
assert_eq!(v.codec, "VP8");
|
||||
assert_eq!((v.width, v.height), (352, 288));
|
||||
assert_eq!(v.color.matrix, filmcraft_color::Matrix::Bt601);
|
||||
assert_eq!(reference.len(), frames * 352 * 288 * 3 / 2);
|
||||
let frame_len = reference.len() / frames;
|
||||
let rate = info.frame_rate();
|
||||
let order: Vec<usize> = [147, 3, 90, 26, 24, 149, 0, 112, 61].into_iter().chain(40..frames).collect();
|
||||
for k in order {
|
||||
let f = src.video_frame(FrameRequest::full(rate.tick_of(k as i64))).unwrap();
|
||||
assert_eq!((f.width, f.height), (352, 288));
|
||||
assert_eq!(f.color.matrix, filmcraft_color::Matrix::Bt601);
|
||||
assert!(yuv_bytes(&f) == reference[k * frame_len..(k + 1) * frame_len], "frame {k} differs from libvpx");
|
||||
}
|
||||
}
|
||||
|
||||
/// #432 / #402: a VP8 WebM with an alpha layer (`AlphaMode` 1, a second VP8 stream in each
|
||||
/// block's `BlockAdditional`, as browsers and GIF converters write transparent WebM) decodes
|
||||
/// with that alpha; frames requested out of order match libvpx exactly, colour and alpha.
|
||||
#[test]
|
||||
fn vp8_webm_alpha_layer_matches_libvpx() {
|
||||
let (w, h, frames) = (176usize, 144usize, 30usize);
|
||||
let Some(path) = fixture_path(
|
||||
"alpha_vp8.webm",
|
||||
&[
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"testsrc2=s=176x144:r=25,format=yuva420p,geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='mod(X*2+N*7\\,256)'",
|
||||
"-frames:v",
|
||||
"30",
|
||||
"-c:v",
|
||||
"libvpx",
|
||||
"-pix_fmt",
|
||||
"yuva420p",
|
||||
"-g",
|
||||
"10",
|
||||
"-auto-alt-ref",
|
||||
"0",
|
||||
"-b:v",
|
||||
"600k",
|
||||
],
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let src_path = path.to_string_lossy().into_owned();
|
||||
let Some(reference) =
|
||||
fixture("alpha_vp8.webm.yuva420p.yuv", &["-c:v", "libvpx", "-i", &src_path, "-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", "yuva420p"])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (luma, chroma) = (w * h, (w / 2) * (h / 2));
|
||||
let frame_len = luma * 2 + chroma * 2;
|
||||
if reference.len() != frame_len * frames {
|
||||
eprintln!("libvpx reference has {} bytes, not {}; skipping", reference.len(), frame_len * frames);
|
||||
return;
|
||||
}
|
||||
let bytes: Arc<[u8]> = std::fs::read(&path).unwrap().into();
|
||||
let src = crate::open_bytes("alpha_vp8.webm", bytes).unwrap();
|
||||
let info = src.info().clone();
|
||||
assert_eq!(info.container, "WebM");
|
||||
assert!(info.video.as_ref().unwrap().has_alpha);
|
||||
let rate = info.frame_rate();
|
||||
let order: Vec<usize> = [29, 3, 17, 12, 0, 25, 21, 9].into_iter().chain(10..frames).collect();
|
||||
for k in order {
|
||||
let f = src.video_frame(FrameRequest::full(rate.tick_of(k as i64))).unwrap();
|
||||
let r = &reference[k * frame_len..(k + 1) * frame_len];
|
||||
let filmcraft_frame::PixelData::Yuv8 { planes, alpha, .. } = &f.data else { panic!("frame {k}: not 8-bit YUV: {}", f.format_label()) };
|
||||
assert_eq!(planes[0].as_slice(), &r[..luma], "frame {k}: luma");
|
||||
assert_eq!(planes[1].as_slice(), &r[luma..luma + chroma], "frame {k}: U");
|
||||
let alpha = alpha.as_ref().unwrap_or_else(|| panic!("frame {k}: no alpha plane"));
|
||||
assert_eq!(alpha.as_slice(), &r[luma + 2 * chroma..], "frame {k}: alpha");
|
||||
}
|
||||
}
|
||||
|
||||
/// Every sample flagged as sync (as in an MP4 without `stss`): the GOP cache must still start
|
||||
/// decoding at a real VP9 key frame.
|
||||
#[test]
|
||||
|
||||
@@ -547,6 +547,60 @@ pub fn vp9_factory(e: &SampleEntry) -> Option<Result<Box<dyn VideoDecoder>>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Our pure-Rust VP8 decoder (RFC 6386; #432): WebM / Matroska `V_VP8` and MP4 `vp08`.
|
||||
pub struct Vp8Decoder {
|
||||
dec: filmcraft_vp8::Decoder,
|
||||
}
|
||||
|
||||
impl Default for Vp8Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Vp8Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self { dec: filmcraft_vp8::Decoder::new() }
|
||||
}
|
||||
|
||||
fn convert(p: filmcraft_vp8::Picture) -> DecodedFrame {
|
||||
use filmcraft_frame::{Chroma, PixelData};
|
||||
use std::sync::Arc;
|
||||
// color_space 0 is BT.601 YUV (RFC 6386 9.2), studio swing; a container's Colour wins
|
||||
let color = filmcraft_color::ColorInfo { matrix: filmcraft_color::Matrix::Bt601, ..filmcraft_color::ColorInfo::REC709 };
|
||||
let data = PixelData::Yuv8 { planes: [Arc::new(p.y), Arc::new(p.u), Arc::new(p.v)], chroma: Chroma::C420, alpha: None };
|
||||
let frame = VideoFrame { width: p.width, height: p.height, data, color, par: (1, 1), pts: filmcraft_time::Tick::ZERO };
|
||||
DecodedFrame { pts: p.pts, frame, draft: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoDecoder for Vp8Decoder {
|
||||
fn decode(&mut self, sample: &[u8], pts: i64) -> Result<Vec<DecodedFrame>> {
|
||||
let pic = self.dec.decode(sample, pts).map_err(|e| CodecError::Decode(e.to_string()))?;
|
||||
Ok(pic.into_iter().map(Self::convert).collect())
|
||||
}
|
||||
fn flush(&mut self) -> Vec<DecodedFrame> {
|
||||
// no reordering: every picture comes out with its frame
|
||||
Vec::new()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.dec.reset();
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
"FilmCraft VP8"
|
||||
}
|
||||
fn is_random_access(&self, sample: &[u8]) -> Option<bool> {
|
||||
Some(filmcraft_vp8::is_keyframe(sample))
|
||||
}
|
||||
fn is_disposable(&self, sample: &[u8]) -> bool {
|
||||
self.dec.is_disposable(sample)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vp8_factory(e: &SampleEntry) -> Option<Result<Box<dyn VideoDecoder>>> {
|
||||
(e.format.0 == *b"vp08").then(|| Ok(Box::new(Vp8Decoder::new()) as Box<dyn VideoDecoder>))
|
||||
}
|
||||
|
||||
/// Our AV1 decoder. The `av1C` configuration OBUs (sequence header) are fed before the first
|
||||
/// sample and again after every reset.
|
||||
pub struct Av1Decoder {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! WebM alpha (#402): a VP9 track with `AlphaMode` 1 carries, in each block's `BlockAdditional`
|
||||
//! with `BlockAddID` 1, a second VP9 stream coded independently of the colour one. Its luma plane,
|
||||
//! WebM alpha (#402): a VP8 or VP9 track with `AlphaMode` 1 carries, in each block's
|
||||
//! `BlockAdditional` with `BlockAddID` 1, a second stream of the same codec coded independently of
|
||||
//! the colour one. Its luma plane,
|
||||
//! full range, is the frame's alpha (the WebM convention, as libvpx-based decoders read it).
|
||||
//!
|
||||
//! [`AlphaDecoder`] wraps the colour decoder: every sample it decodes, it also decodes that
|
||||
//! sample's alpha frame with a second (software) VP9 decoder and attaches the luma plane to the
|
||||
//! sample's alpha frame with a second (software) decoder and attaches the luma plane to the
|
||||
//! colour picture with the same timestamp, holding a colour picture back until its alpha is out. The alpha stream has key frames of its own, which need
|
||||
//! not coincide with the colour ones: after a seek (or any other break in the order samples are
|
||||
//! fed), the alpha decoder restarts from the nearest alpha key frame at or before the sample.
|
||||
@@ -21,7 +22,7 @@ use crate::video::{DecodedFrame, VideoDecoder};
|
||||
/// How far back the alpha decoder looks for a key frame when it resynchronises.
|
||||
const MAX_RESYNC: usize = 600;
|
||||
/// Decoded alpha pictures waiting for their colour picture, and colour pictures waiting for their
|
||||
/// alpha (VP9 pictures come out in order, so a few are plenty; past that a picture goes out opaque
|
||||
/// alpha (VP8 / VP9 pictures come out in order, so a few are plenty; past that a picture goes out opaque
|
||||
/// rather than stall).
|
||||
const MAX_PENDING: usize = 8;
|
||||
|
||||
@@ -106,7 +107,7 @@ impl AlphaDecoder {
|
||||
self.alpha.reset();
|
||||
self.pending.clear();
|
||||
self.alpha_max = None;
|
||||
let is_key = |k: usize| self.layer.read(k).is_some_and(|d| filmcraft_vp9::is_keyframe(&d));
|
||||
let is_key = |k: usize| self.layer.read(k).is_some_and(|d| self.alpha.is_random_access(&d) == Some(true));
|
||||
let start = (i.saturating_sub(MAX_RESYNC)..=i).rev().find(|&k| is_key(k));
|
||||
if let Some(start) = start {
|
||||
for k in start..i {
|
||||
|
||||
@@ -1279,7 +1279,7 @@ pub fn system_report() -> Value {
|
||||
"family": std::env::consts::FAMILY,
|
||||
"arch": std::env::consts::ARCH,
|
||||
"cpuThreads": cpus,
|
||||
"decoders": ["H.264 (AVC)", "H.265 (HEVC) Main / Main 10", "VP9", "AV1 Main", "Apple ProRes", "Avid DNxHD / DNxHR", "Motion JPEG", "AAC-LC", "Opus", "PCM"],
|
||||
"decoders": ["H.264 (AVC)", "H.265 (HEVC) Main / Main 10", "VP8", "VP9", "AV1 Main", "Apple ProRes", "Avid DNxHD / DNxHR", "Motion JPEG", "AAC-LC", "Opus", "PCM"],
|
||||
"containers": ["MP4 / MOV (ISO BMFF)", "Matroska / WebM", "WAV", "PNG / JPEG stills"],
|
||||
"exportFormats": formats,
|
||||
"speechToText": crate::transcript::speech_available(),
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "filmcraft-vp8"
|
||||
description = "Clean-room pure-Rust VP8 decoder (RFC 6386)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
@@ -0,0 +1,72 @@
|
||||
# filmcraft-vp8
|
||||
|
||||
Clean-room, pure-Rust (no `unsafe`) VP8 decoder, implemented from RFC 6386, *VP8 Data Format and
|
||||
Decoding Guide*, sections 1-19. It is an L0 crate: it depends only on `thiserror`, and builds for
|
||||
`wasm32-unknown-unknown`.
|
||||
|
||||
The constant tables (coefficient and mode probabilities and their update probabilities, the
|
||||
subblock mode contexts, quantizer lookups, MV probabilities, interpolation filters...) were
|
||||
extracted from the text of the RFC by [`tools/extract_tables.py`](tools/extract_tables.py) into
|
||||
`src/spec_tables.rs`; the script cuts the text at section 20, so nothing is read from the
|
||||
reference decoder attached to the RFC. No code was taken or ported from libvpx, FFmpeg or any
|
||||
other decoder; ffmpeg (with libvpx) and vpxenc are used only as external fixture generators and
|
||||
test oracles.
|
||||
|
||||
## Status
|
||||
|
||||
| Area | Status |
|
||||
|---|---|
|
||||
| Frame tag, key frame header, frame header (segmentation, loop filter type / level / sharpness, deltas, partitions, quantizer indices, reference updates and sign bias, probability updates) | done |
|
||||
| Boolean decoder, tree decoding, probability persistence across frames and `refresh_entropy_probs` | done |
|
||||
| Intra prediction: 16x16 and chroma modes, the ten 4x4 subblock modes, key frame subblock mode contexts, edge rules | done |
|
||||
| Inter prediction: last / golden / altref with sign bias, NEAREST / NEAR / ZERO / NEW / SPLIT (all four partitionings), MV decoding and clamping, six-tap and bilinear filters, full-pixel chroma (bitstream versions 0-3) | done |
|
||||
| Coefficient tokens (1-8 partitions), dequantization (frame and segment quantizers), inverse WHT and DCT | done |
|
||||
| Normal and simple loop filters, per-macroblock levels from segments and reference / mode deltas | done |
|
||||
| Golden / altref copies, hidden (altref) frames, frame size changes at key frames | done |
|
||||
| Damaged input: errors (never panics), decoding resumes at the next key frame | done |
|
||||
| Upscaling hints of the key frame header (9.1) | not applied (reported in `KeyframeInfo`) |
|
||||
|
||||
Bit-exact with libvpx on the 62 libvpx VP8 test vectors checked (`vp80-00-comprehensive-001` to
|
||||
`018` and the `vp80-01` to `vp80-06` intra, inter, segmentation, partition, sharpness and small
|
||||
size vectors; `tests/vectors.rs` checks them when they are present) and on the generated fixtures
|
||||
of `tests/conformance.rs`.
|
||||
|
||||
## API
|
||||
|
||||
```rust
|
||||
use filmcraft_vp8::Decoder;
|
||||
|
||||
let mut dec = Decoder::new();
|
||||
for (pts, frame) in frames { // one IVF frame / WebM block / MP4 sample each
|
||||
let skippable = dec.is_disposable(frame); // could this frame be left out (catching up)?
|
||||
if let Some(pic) = dec.decode(frame, pts)? {
|
||||
// pic.width / pic.height, pic.chroma_width / chroma_height,
|
||||
// pic.y / pic.u / pic.v: tight 8-bit 4:2:0 planes, pic.pts, pic.key,
|
||||
// pic.color_space (0: BT.601 YUV)
|
||||
}
|
||||
// None: a hidden (altref) frame
|
||||
}
|
||||
dec.reset(); // forget the reference frames (seek): decoding resumes at the next key frame
|
||||
let stats = dec.stats(); // frames, key frames, hidden frames
|
||||
|
||||
// Random access points
|
||||
let key: bool = filmcraft_vp8::is_keyframe(frame);
|
||||
let info: Option<filmcraft_vp8::KeyframeInfo> = filmcraft_vp8::keyframe_info(frame);
|
||||
```
|
||||
|
||||
`filmcraft-codecs` wires the decoder into Matroska / WebM (`V_VP8`, including the alpha layer of
|
||||
transparent WebM in `BlockAdditional`) and MP4 (`vp08`). VP8 has no colour signalling: pictures are
|
||||
BT.601, studio swing (RFC 6386 9.2), unless the container's `Colour` says otherwise. VP8 has no
|
||||
superframes either: a hidden altref frame is a block of its own, and the WebM reader takes a
|
||||
request for its time as a request for the shown frame that follows it.
|
||||
|
||||
## Tests and tools
|
||||
|
||||
- `cargo test -p filmcraft-vp8`: unit tests, bit-exactness against libvpx on generated fixtures
|
||||
(`target/fixtures/vp8-tests`, skipped without ffmpeg / libvpx), robustness (corrupted,
|
||||
truncated, reordered and random input), and the libvpx test vectors when present in
|
||||
`target/fixtures/vp8/conformance` (or `$VP8_TEST_VECTORS`).
|
||||
- `cargo run --release -p filmcraft-vp8 --example vp8dec in.ivf out.yuv`: decode to raw I420.
|
||||
- `cargo run -p filmcraft-vp8 --example fuzz -- <iterations> <seed> files.ivf...`: decode
|
||||
randomly damaged variants of IVF files (in a debug build, so overflow panics too) and report
|
||||
any panic.
|
||||
@@ -0,0 +1,100 @@
|
||||
//! Decode corrupted variants of IVF files and report panics (`fuzz iterations seed files...`).
|
||||
//! Run in a debug build so arithmetic overflow panics too.
|
||||
|
||||
fn frames(data: &[u8]) -> Vec<Vec<u8>> {
|
||||
let mut out = Vec::new();
|
||||
let Some(h) = data.get(6..8) else { return out };
|
||||
let mut pos = usize::from(u16::from_le_bytes([h[0], h[1]]));
|
||||
while pos + 12 <= data.len() {
|
||||
let size = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 12;
|
||||
out.push(data[pos..(pos + size).min(data.len())].to_vec());
|
||||
pos += size;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let iterations: usize = args[1].parse().unwrap_or(100);
|
||||
let mut seed: u64 = args[2].parse().unwrap_or(1);
|
||||
let mut rnd = move || {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 7;
|
||||
seed ^= seed << 17;
|
||||
seed
|
||||
};
|
||||
std::panic::set_hook(Box::new(|_| {}));
|
||||
let mut panics = 0;
|
||||
let mut decoded = 0u64;
|
||||
for path in &args[3..] {
|
||||
let original = frames(&std::fs::read(path).unwrap_or_default());
|
||||
for it in 0..iterations {
|
||||
let mut fr = original.clone();
|
||||
let mode = rnd() % 6;
|
||||
for f in fr.iter_mut() {
|
||||
if f.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match mode {
|
||||
0 => {
|
||||
// a few bit flips per frame
|
||||
for _ in 0..1 + rnd() % 4 {
|
||||
let i = (rnd() as usize) % f.len();
|
||||
f[i] ^= 1 << (rnd() % 8);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
let n = (rnd() as usize) % (f.len() + 1);
|
||||
f.truncate(n);
|
||||
}
|
||||
2 => {
|
||||
// random bytes in the header region (frame tag, sizes, first partition)
|
||||
for _ in 0..1 + rnd() % 3 {
|
||||
let i = (rnd() as usize) % f.len().min(24);
|
||||
f[i] = rnd() as u8;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for b in f.iter_mut().skip(10) {
|
||||
if rnd() % 50 == 0 {
|
||||
*b = rnd() as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
// trailing garbage on some frames
|
||||
4 if rnd() % 3 == 0 => f.extend((0..rnd() % 64).map(|_| rnd() as u8)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if mode == 5 {
|
||||
// decode order shuffled: inter frames after the wrong references
|
||||
for i in (1..fr.len()).rev() {
|
||||
let j = (rnd() as usize) % (i + 1);
|
||||
fr.swap(i, j);
|
||||
}
|
||||
}
|
||||
let r = std::panic::catch_unwind(|| {
|
||||
let mut dec = filmcraft_vp8::Decoder::new();
|
||||
let mut n = 0u64;
|
||||
for (k, f) in fr.iter().enumerate() {
|
||||
let _ = dec.is_disposable(f);
|
||||
if dec.decode(f, k as i64).is_ok() {
|
||||
n += 1;
|
||||
}
|
||||
let _ = filmcraft_vp8::keyframe_info(f);
|
||||
}
|
||||
n
|
||||
});
|
||||
match r {
|
||||
Ok(n) => decoded += n,
|
||||
Err(e) => {
|
||||
panics += 1;
|
||||
let msg = e.downcast_ref::<String>().cloned().or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default();
|
||||
eprintln!("PANIC {path} iteration {it} mode {mode}: {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("done: {panics} panics, {decoded} frames decoded");
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//! Decode an IVF file to raw I420 (`vp8dec in.ivf out.yuv`), for comparison with other decoders.
|
||||
//! With `VP8DEC_SIZES` set, prints the size of each picture.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let [_, input, output] = args.as_slice() else {
|
||||
return Err("usage: vp8dec in.ivf out.yuv".into());
|
||||
};
|
||||
let data = std::fs::read(input)?;
|
||||
let hdr_len = usize::from(u16::from_le_bytes([data[6], data[7]]));
|
||||
let mut pos = hdr_len;
|
||||
let mut out = std::io::BufWriter::new(std::fs::File::create(output)?);
|
||||
let mut dec = filmcraft_vp8::Decoder::new();
|
||||
let mut n = 0;
|
||||
let t = std::time::Instant::now();
|
||||
while pos + 12 <= data.len() {
|
||||
let size = u32::from_le_bytes(data[pos..pos + 4].try_into()?) as usize;
|
||||
let pts = i64::from_le_bytes(data[pos + 4..pos + 12].try_into()?);
|
||||
pos += 12;
|
||||
let frame = &data[pos..(pos + size).min(data.len())];
|
||||
pos += size;
|
||||
match dec.decode(frame, pts) {
|
||||
Ok(Some(p)) => {
|
||||
if std::env::var_os("VP8DEC_SIZES").is_some() {
|
||||
println!("{} {}", p.width, p.height);
|
||||
}
|
||||
out.write_all(&p.y)?;
|
||||
out.write_all(&p.u)?;
|
||||
out.write_all(&p.v)?;
|
||||
n += 1;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => eprintln!("frame at pts {pts}: {e}"),
|
||||
}
|
||||
}
|
||||
eprintln!("{n} pictures in {:.2}s; {:?}", t.elapsed().as_secs_f64(), dec.stats());
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
//! Boolean entropy decoder (RFC 6386 section 7).
|
||||
//!
|
||||
//! The specification keeps two bytes of the input in `value` and shifts in one byte every eight
|
||||
//! renormalisation steps. This implementation keeps the active bits at the top of a 64-bit window
|
||||
//! whose lower bits hold the next bytes of the input, which is equivalent and refills only every
|
||||
//! few bytes. Reading past the end of the data yields zero bits.
|
||||
|
||||
pub struct BoolDecoder<'a> {
|
||||
data: &'a [u8],
|
||||
pos: usize,
|
||||
/// The bits being decoded, most significant first: the top 8 bits correspond to the
|
||||
/// specification's `value >> 8`, followed by `count` further bits of input.
|
||||
value: u64,
|
||||
count: i32,
|
||||
range: u32,
|
||||
}
|
||||
|
||||
impl<'a> BoolDecoder<'a> {
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
let mut d = BoolDecoder { data, pos: 0, value: 0, count: -8, range: 255 };
|
||||
d.fill();
|
||||
d
|
||||
}
|
||||
|
||||
fn fill(&mut self) {
|
||||
let mut shift = 48 - self.count;
|
||||
while shift >= 0 {
|
||||
match self.data.get(self.pos) {
|
||||
Some(&b) => {
|
||||
self.pos += 1;
|
||||
self.value |= u64::from(b) << shift;
|
||||
self.count += 8;
|
||||
shift -= 8;
|
||||
}
|
||||
None => {
|
||||
// past the end: zero bits for as long as anyone reads
|
||||
self.count += 0x4000;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `read_bool` (7.3): a bool whose probability of being zero is `p / 256`.
|
||||
#[inline(always)]
|
||||
pub fn read_bool(&mut self, p: u8) -> bool {
|
||||
let split = 1 + (((self.range - 1) * u32::from(p)) >> 8);
|
||||
if self.count < 0 {
|
||||
self.fill();
|
||||
}
|
||||
let big_split = u64::from(split) << 56;
|
||||
let bit = if self.value >= big_split {
|
||||
self.range -= split;
|
||||
self.value -= big_split;
|
||||
true
|
||||
} else {
|
||||
self.range = split;
|
||||
false
|
||||
};
|
||||
// renormalise: shift until range >= 128
|
||||
let shift = self.range.leading_zeros().saturating_sub(24);
|
||||
self.range <<= shift;
|
||||
self.value <<= shift;
|
||||
self.count -= shift as i32;
|
||||
bit
|
||||
}
|
||||
|
||||
/// `read_literal` (7.3): an `n`-bit unsigned value, most significant bit first, each bit at
|
||||
/// probability 1/2.
|
||||
#[inline]
|
||||
pub fn read_literal(&mut self, n: u32) -> u32 {
|
||||
let mut v = 0;
|
||||
for _ in 0..n {
|
||||
v = (v << 1) | u32::from(self.read_bool(128));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// An `L(1)` flag.
|
||||
#[inline]
|
||||
pub fn read_flag(&mut self) -> bool {
|
||||
self.read_bool(128)
|
||||
}
|
||||
|
||||
/// A magnitude `L(n)` followed by a sign `L(1)` (1 = negative), as the frame header codes its
|
||||
/// signed fields.
|
||||
pub fn read_signed(&mut self, n: u32) -> i32 {
|
||||
let v = self.read_literal(n) as i32;
|
||||
if self.read_flag() { -v } else { v }
|
||||
}
|
||||
|
||||
/// An optional signed field: a flag, then [`Self::read_signed`] when it is set, else 0.
|
||||
pub fn read_optional_signed(&mut self, n: u32) -> i32 {
|
||||
if self.read_flag() { self.read_signed(n) } else { 0 }
|
||||
}
|
||||
|
||||
/// `treed_read` (8.1): a tree-coded value. `tree` uses the specification's layout: positive
|
||||
/// entries index deeper nodes, non-positive entries `-v` are leaves of value `v`; `probs[i]`
|
||||
/// is the probability of node `2 * i`.
|
||||
#[inline]
|
||||
pub fn read_tree(&mut self, tree: &[i8], probs: &[u8]) -> u8 {
|
||||
self.read_tree_from(tree, probs, 0)
|
||||
}
|
||||
|
||||
/// [`Self::read_tree`] starting at node `start` instead of the root.
|
||||
#[inline]
|
||||
pub fn read_tree_from(&mut self, tree: &[i8], probs: &[u8], start: usize) -> u8 {
|
||||
let mut i = start;
|
||||
loop {
|
||||
let p = probs.get(i >> 1).copied().unwrap_or(128);
|
||||
let next = tree.get(i + usize::from(self.read_bool(p))).copied().unwrap_or(0);
|
||||
if next <= 0 {
|
||||
return (-next) as u8;
|
||||
}
|
||||
i = next as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The encoder of section 7.3, to round-trip the decoder.
|
||||
struct BoolEncoder {
|
||||
out: Vec<u8>,
|
||||
range: u32,
|
||||
bottom: u32,
|
||||
bit_count: i32,
|
||||
}
|
||||
|
||||
impl BoolEncoder {
|
||||
fn new() -> Self {
|
||||
BoolEncoder { out: Vec::new(), range: 255, bottom: 0, bit_count: 24 }
|
||||
}
|
||||
fn add_one(&mut self) {
|
||||
let mut i = self.out.len();
|
||||
while i > 0 {
|
||||
i -= 1;
|
||||
if self.out[i] == 255 {
|
||||
self.out[i] = 0;
|
||||
} else {
|
||||
self.out[i] += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn write(&mut self, p: u8, bit: bool) {
|
||||
let split = 1 + (((self.range - 1) * u32::from(p)) >> 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.add_one();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn finish(mut self) -> Vec<u8> {
|
||||
let mut c = self.bit_count;
|
||||
let mut v = self.bottom;
|
||||
if v & (1u32.wrapping_shl((32 - c) as u32)) != 0 {
|
||||
self.add_one();
|
||||
}
|
||||
v <<= c & 7;
|
||||
c >>= 3;
|
||||
while c > 0 {
|
||||
c -= 1;
|
||||
v <<= 8;
|
||||
}
|
||||
for _ in 0..4 {
|
||||
self.out.push((v >> 24) as u8);
|
||||
v <<= 8;
|
||||
}
|
||||
self.out
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_bools_at_every_probability() {
|
||||
let mut seed = 0x1234_5678u32;
|
||||
let mut rnd = || {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 17;
|
||||
seed ^= seed << 5;
|
||||
seed
|
||||
};
|
||||
let bits: Vec<(u8, bool)> = (0..20_000).map(|_| (((rnd() % 255) + 1) as u8, rnd() % 3 == 0)).collect();
|
||||
let mut e = BoolEncoder::new();
|
||||
for &(p, b) in &bits {
|
||||
e.write(p, b);
|
||||
}
|
||||
let data = e.finish();
|
||||
let mut d = BoolDecoder::new(&data);
|
||||
for (i, &(p, b)) in bits.iter().enumerate() {
|
||||
assert_eq!(d.read_bool(p), b, "bool {i}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_zeros_past_the_end() {
|
||||
let mut d = BoolDecoder::new(&[]);
|
||||
for _ in 0..1000 {
|
||||
assert!(!d.read_bool(128));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
//! Frame decoding: macroblock prediction records (11, 16, 17), coefficients (13), reconstruction
|
||||
//! (12, 14, 18), the loop filter (15) and reference frame management (9.7, 9.8).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Picture;
|
||||
use crate::boolcoder::BoolDecoder;
|
||||
use crate::error::{Error, Result, ensure};
|
||||
use crate::header::{EntropyContext, FrameHeader, FrameTag, LfDeltas, Segmentation, read_header};
|
||||
use crate::loopfilter::{self, Kind, Params};
|
||||
use crate::predict::{self, RefPlane};
|
||||
use crate::spec_tables::*;
|
||||
use crate::tables::*;
|
||||
use crate::transform;
|
||||
|
||||
/// Largest frame dimension in macroblocks (14-bit sizes).
|
||||
const MAX_MBS: usize = 16384 / 16 + 1;
|
||||
|
||||
/// A reconstructed frame: planes sized to whole macroblocks.
|
||||
#[derive(Clone)]
|
||||
struct Frame {
|
||||
y: Vec<u8>,
|
||||
u: Vec<u8>,
|
||||
v: Vec<u8>,
|
||||
/// Luma width / height (multiples of 16); chroma is half.
|
||||
w: usize,
|
||||
h: usize,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
fn new(w: usize, h: usize) -> Frame {
|
||||
Frame { y: vec![0; w * h], u: vec![0; w * h / 4], v: vec![0; w * h / 4], w, h }
|
||||
}
|
||||
}
|
||||
|
||||
/// A motion vector in quarter luma pixels.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
|
||||
struct Mv {
|
||||
row: i32,
|
||||
col: i32,
|
||||
}
|
||||
|
||||
impl Mv {
|
||||
const ZERO: Mv = Mv { row: 0, col: 0 };
|
||||
fn is_zero(self) -> bool {
|
||||
self == Mv::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
/// What later macroblocks (and the loop filter) need to know about a decoded macroblock.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct MbInfo {
|
||||
/// Intra mode (`DC_PRED`..`B_PRED`) or inter mode (`MV_NEAREST`..`MV_SPLIT`).
|
||||
mode: u8,
|
||||
/// 0 intra, 1 last, 2 golden, 3 altref.
|
||||
ref_frame: u8,
|
||||
mv: Mv,
|
||||
/// Motion vector of each Y subblock (all `mv` unless split).
|
||||
bmv: [Mv; 16],
|
||||
}
|
||||
|
||||
/// Statistics of the frames decoded so far.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct DecodeStats {
|
||||
pub frames: u64,
|
||||
pub key_frames: u64,
|
||||
pub hidden_frames: u64,
|
||||
}
|
||||
|
||||
/// Dequantization factors of one segment: [DC, AC] for Y, Y2 and chroma (14.1).
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct Dequant {
|
||||
y: [i32; 2],
|
||||
y2: [i32; 2],
|
||||
uv: [i32; 2],
|
||||
}
|
||||
|
||||
impl Dequant {
|
||||
fn new(q: i32, h: &FrameHeader) -> Dequant {
|
||||
let dc = |d: i32| i32::from(DC_QLOOKUP[(q + d).clamp(0, 127) as usize]);
|
||||
let ac = |d: i32| i32::from(AC_QLOOKUP[(q + d).clamp(0, 127) as usize]);
|
||||
let qi = &h.quant;
|
||||
Dequant {
|
||||
y: [dc(qi.y_dc_delta), ac(0)],
|
||||
y2: [dc(qi.y2_dc_delta) * 2, (ac(qi.y2_ac_delta) * 155 / 100).max(8)],
|
||||
uv: [dc(qi.uv_dc_delta).min(132), ac(qi.uv_ac_delta)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// VP8 decoder (RFC 6386). Feed it one compressed frame at a time ([`Decoder::decode`]).
|
||||
pub struct Decoder {
|
||||
ctx: EntropyContext,
|
||||
seg: Segmentation,
|
||||
lf: LfDeltas,
|
||||
/// Segment of each macroblock (persists while the map is not updated).
|
||||
seg_map: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
mb_w: usize,
|
||||
mb_h: usize,
|
||||
/// Last, golden and altref frames.
|
||||
refs: [Option<Arc<Frame>>; 3],
|
||||
/// `ref_frame_sign_bias`, indexed by reference frame (0 intra, 1 last, 2 golden, 3 altref).
|
||||
sign_bias: [bool; 4],
|
||||
stats: DecodeStats,
|
||||
}
|
||||
|
||||
impl Default for Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
pub fn new() -> Decoder {
|
||||
Decoder {
|
||||
ctx: EntropyContext::default(),
|
||||
seg: Segmentation::default(),
|
||||
lf: LfDeltas::default(),
|
||||
seg_map: Vec::new(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
mb_w: 0,
|
||||
mb_h: 0,
|
||||
refs: [None, None, None],
|
||||
sign_bias: [false; 4],
|
||||
stats: DecodeStats::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Forget the reference frames (after a seek): decoding resumes at the next key frame.
|
||||
pub fn reset(&mut self) {
|
||||
self.refs = [None, None, None];
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> DecodeStats {
|
||||
self.stats
|
||||
}
|
||||
|
||||
/// Whether leaving `frame` out, decoded next, would change no other picture: an inter frame
|
||||
/// that refreshes and copies no reference buffer, does not update the segment map, and leaves
|
||||
/// the probabilities, segment data and loop filter deltas as they are now (or keeps its
|
||||
/// probability updates to itself). Only the frame header is read.
|
||||
pub fn is_disposable(&self, frame: &[u8]) -> bool {
|
||||
let Ok(tag) = FrameTag::parse(frame) else { return false };
|
||||
if tag.key_frame || self.refs[0].is_none() {
|
||||
return false;
|
||||
}
|
||||
let Some(first) = frame.get(tag.header_len..tag.header_len.saturating_add(tag.first_part_size)) else { return false };
|
||||
let mut d = BoolDecoder::new(first);
|
||||
let (mut seg, mut lf, mut ctx, mut saved) = (self.seg, self.lf, self.ctx.clone(), None);
|
||||
let h = read_header(&mut d, false, &mut seg, &mut lf, &mut ctx, &mut saved);
|
||||
let refs_untouched = !h.refresh_golden && !h.refresh_altref && !h.refresh_last && h.copy_to_golden == 0 && h.copy_to_altref == 0;
|
||||
let probs_kept = !h.refresh_entropy || ctx == self.ctx;
|
||||
let segments_kept = !seg.update_map && seg.absolute == self.seg.absolute && seg.quant == self.seg.quant && seg.lf_level == self.seg.lf_level;
|
||||
let deltas_kept = lf.ref_deltas == self.lf.ref_deltas && lf.mode_deltas == self.lf.mode_deltas;
|
||||
refs_untouched && probs_kept && segments_kept && deltas_kept
|
||||
}
|
||||
|
||||
/// Decode one compressed frame. Returns the picture when the frame is meant to be shown
|
||||
/// (`show_frame`), `None` for a hidden frame (an altref).
|
||||
pub fn decode(&mut self, data: &[u8], pts: i64) -> Result<Option<Picture>> {
|
||||
let tag = FrameTag::parse(data)?;
|
||||
if tag.key_frame {
|
||||
ensure!(tag.width > 0 && tag.height > 0, "key frame of size {}x{}", tag.width, tag.height);
|
||||
let (w, h) = (usize::from(tag.width), usize::from(tag.height));
|
||||
if w != self.width || h != self.height {
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
self.mb_w = w.div_ceil(16);
|
||||
self.mb_h = h.div_ceil(16);
|
||||
self.seg_map = vec![0; self.mb_w * self.mb_h];
|
||||
}
|
||||
// key frame defaults (9.3, 9.4, 9.7, 13.4, 16.1, 17.2)
|
||||
self.ctx = EntropyContext::default();
|
||||
self.seg.absolute = false;
|
||||
self.seg.quant = [0; 4];
|
||||
self.seg.lf_level = [0; 4];
|
||||
self.lf.ref_deltas = [0; 4];
|
||||
self.lf.mode_deltas = [0; 4];
|
||||
self.sign_bias = [false; 4];
|
||||
} else if self.refs[0].is_none() {
|
||||
return Err(Error::MissingReference("inter frame before the first key frame".into()));
|
||||
}
|
||||
ensure!(self.mb_w > 0 && self.mb_w <= MAX_MBS && self.mb_h <= MAX_MBS, "no frame size");
|
||||
let first_start = tag.header_len;
|
||||
let first_end = first_start.saturating_add(tag.first_part_size);
|
||||
ensure!(first_end <= data.len(), "first partition ({} bytes) runs past the frame ({} bytes)", tag.first_part_size, data.len());
|
||||
let mut bd = BoolDecoder::new(&data[first_start..first_end]);
|
||||
let mut saved = None;
|
||||
let hdr = read_header(&mut bd, tag.key_frame, &mut self.seg, &mut self.lf, &mut self.ctx, &mut saved);
|
||||
if !tag.key_frame {
|
||||
self.sign_bias[2] = hdr.sign_bias_golden;
|
||||
self.sign_bias[3] = hdr.sign_bias_altref;
|
||||
}
|
||||
|
||||
// token partitions (9.5): sizes of all but the last, then the data
|
||||
let n = hdr.partitions;
|
||||
let sizes_start = first_end;
|
||||
let mut pos = sizes_start.saturating_add(3 * (n - 1)).min(data.len());
|
||||
let mut parts: Vec<&[u8]> = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let size = if i + 1 < n {
|
||||
let s = sizes_start + 3 * i;
|
||||
match data.get(s..s + 3) {
|
||||
Some(b) => usize::from(b[0]) | usize::from(b[1]) << 8 | usize::from(b[2]) << 16,
|
||||
None => 0,
|
||||
}
|
||||
} else {
|
||||
data.len() - pos
|
||||
};
|
||||
let end = pos.saturating_add(size).min(data.len());
|
||||
parts.push(&data[pos..end]);
|
||||
pos = end;
|
||||
}
|
||||
let mut token_decoders: Vec<BoolDecoder> = parts.into_iter().map(BoolDecoder::new).collect();
|
||||
|
||||
let mut frame = Frame::new(self.mb_w * 16, self.mb_h * 16);
|
||||
let dequant: [Dequant; 4] = std::array::from_fn(|s| {
|
||||
let q = if self.seg.enabled {
|
||||
let v = i32::from(self.seg.quant[s]);
|
||||
if self.seg.absolute { v } else { hdr.quant.y_ac + v }
|
||||
} else {
|
||||
hdr.quant.y_ac
|
||||
};
|
||||
Dequant::new(q.clamp(0, 127), &hdr)
|
||||
});
|
||||
let filters: &[i32; 48] = if tag.version == 0 || tag.version > 3 { &SIXTAP_FILTERS } else { &BILINEAR_FILTERS };
|
||||
let full_pixel = tag.version == 3;
|
||||
|
||||
let stride_info = self.mb_w + 1;
|
||||
let mut infos = vec![MbInfo::default(); stride_info * (self.mb_h + 1)];
|
||||
let mut lf_info: Vec<(u8, bool)> = vec![(0, false); self.mb_w * self.mb_h];
|
||||
// contexts: B_PRED modes above / left (key frames), non-zero flags above / left
|
||||
let mut above_bmodes = vec![[B_DC_PRED; 4]; self.mb_w];
|
||||
let mut above_nz = vec![[0u8; 9]; self.mb_w];
|
||||
let refs = self.refs.clone();
|
||||
|
||||
for mb_y in 0..self.mb_h {
|
||||
let mut left_bmodes = [B_DC_PRED; 4];
|
||||
let mut left_nz = [0u8; 9];
|
||||
let part = mb_y % n;
|
||||
for mb_x in 0..self.mb_w {
|
||||
let idx = (mb_y + 1) * stride_info + mb_x + 1;
|
||||
// ---- prediction record (first partition)
|
||||
let seg_idx = mb_y * self.mb_w + mb_x;
|
||||
if self.seg.update_map {
|
||||
let s = bd.read_tree(&SEGMENT_TREE, &self.seg.tree_probs);
|
||||
if let Some(m) = self.seg_map.get_mut(seg_idx) {
|
||||
*m = s;
|
||||
}
|
||||
}
|
||||
let segment = if self.seg.enabled { self.seg_map.get(seg_idx).copied().unwrap_or(0) as usize & 3 } else { 0 };
|
||||
let coeff_skip = hdr.skip_prob.is_some_and(|p| bd.read_bool(p));
|
||||
let mut bmodes = [B_DC_PRED; 16];
|
||||
let mut uv_mode = DC_PRED;
|
||||
let mut info = MbInfo::default();
|
||||
if tag.key_frame {
|
||||
info.mode = bd.read_tree(&KF_YMODE_TREE, &KF_YMODE_PROBS);
|
||||
if info.mode == B_PRED {
|
||||
for b in 0..16 {
|
||||
let a = if b < 4 { above_bmodes[mb_x][b] } else { bmodes[b - 4] };
|
||||
let l = if b & 3 == 0 { left_bmodes[b >> 2] } else { bmodes[b - 1] };
|
||||
let o = (usize::from(a) * 10 + usize::from(l)) * 9;
|
||||
bmodes[b] = bd.read_tree(&BMODE_TREE, &KF_BMODE_PROBS[o..o + 9]);
|
||||
}
|
||||
} else {
|
||||
bmodes = [implied_bmode(info.mode); 16];
|
||||
}
|
||||
uv_mode = bd.read_tree(&UV_MODE_TREE, &KF_UV_MODE_PROBS);
|
||||
} else if bd.read_bool(hdr.prob_intra) {
|
||||
info.ref_frame = if bd.read_bool(hdr.prob_last) { 2 + u8::from(bd.read_bool(hdr.prob_golden)) } else { 1 };
|
||||
self.read_inter_modes(&mut bd, &infos, idx, stride_info, mb_x, mb_y, &mut info);
|
||||
} else {
|
||||
info.mode = bd.read_tree(&YMODE_TREE, &self.ctx.ymode);
|
||||
if info.mode == B_PRED {
|
||||
for m in &mut bmodes {
|
||||
*m = bd.read_tree(&BMODE_TREE, &BMODE_PROBS);
|
||||
}
|
||||
}
|
||||
uv_mode = bd.read_tree(&UV_MODE_TREE, &self.ctx.uv_mode);
|
||||
}
|
||||
above_bmodes[mb_x] = [bmodes[12], bmodes[13], bmodes[14], bmodes[15]];
|
||||
left_bmodes = [bmodes[3], bmodes[7], bmodes[11], bmodes[15]];
|
||||
if let Some(slot) = infos.get_mut(idx) {
|
||||
*slot = info;
|
||||
}
|
||||
|
||||
// ---- residual (token partition)
|
||||
let has_y2 = info.mode != B_PRED && info.mode != MV_SPLIT;
|
||||
let mut coeffs = [[0i16; 16]; 25];
|
||||
let mut nonzero = [false; 25];
|
||||
let td = &mut token_decoders[part];
|
||||
let mut has_coeffs = false;
|
||||
if coeff_skip {
|
||||
let keep_y2 = if has_y2 { 0 } else { left_nz[8] };
|
||||
let keep_y2_above = if has_y2 { 0 } else { above_nz[mb_x][8] };
|
||||
left_nz = [0; 9];
|
||||
above_nz[mb_x] = [0; 9];
|
||||
left_nz[8] = keep_y2;
|
||||
above_nz[mb_x][8] = keep_y2_above;
|
||||
} else {
|
||||
let dq = &dequant[segment];
|
||||
let an = &mut above_nz[mb_x];
|
||||
let first = if has_y2 {
|
||||
let ctx = usize::from(an[8] + left_nz[8]);
|
||||
let eob = read_coefficients(td, &self.ctx.coeff[1], ctx, 0, dq.y2, &mut coeffs[24]);
|
||||
let nz = u8::from(eob > 0);
|
||||
an[8] = nz;
|
||||
left_nz[8] = nz;
|
||||
nonzero[24] = eob > 0;
|
||||
has_coeffs |= eob > 0;
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let y_probs = if has_y2 { &self.ctx.coeff[0] } else { &self.ctx.coeff[3] };
|
||||
for b in 0..16 {
|
||||
let (bx, by) = (b & 3, b >> 2);
|
||||
let ctx = usize::from(an[bx] + left_nz[by]);
|
||||
let eob = read_coefficients(td, y_probs, ctx, first, dq.y, &mut coeffs[b]);
|
||||
let nz = eob > first;
|
||||
an[bx] = u8::from(nz);
|
||||
left_nz[by] = u8::from(nz);
|
||||
nonzero[b] = nz;
|
||||
has_coeffs |= nz;
|
||||
}
|
||||
for b in 16..24 {
|
||||
let (off, k) = if b < 20 { (4, b - 16) } else { (6, b - 20) };
|
||||
let (bx, by) = (k & 1, k >> 1);
|
||||
let ctx = usize::from(an[off + bx] + left_nz[off + by]);
|
||||
let eob = read_coefficients(td, &self.ctx.coeff[2], ctx, 0, dq.uv, &mut coeffs[b]);
|
||||
let nz = eob > 0;
|
||||
an[off + bx] = u8::from(nz);
|
||||
left_nz[off + by] = u8::from(nz);
|
||||
nonzero[b] = nz;
|
||||
has_coeffs |= nz;
|
||||
}
|
||||
if has_y2 && nonzero[24] {
|
||||
let dc = transform::inverse_wht(&coeffs[24]);
|
||||
for b in 0..16 {
|
||||
coeffs[b][0] = dc[b];
|
||||
nonzero[b] |= dc[b] != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- loop filter level (15.1, 9.3, 9.4)
|
||||
let level = self.filter_level(&hdr, segment, &info);
|
||||
let inner = !has_y2 || has_coeffs;
|
||||
if let Some(l) = lf_info.get_mut(seg_idx) {
|
||||
*l = (level, inner);
|
||||
}
|
||||
|
||||
// ---- reconstruction
|
||||
let (px, py) = (mb_x * 16, mb_y * 16);
|
||||
let ys = frame.w;
|
||||
let cs = frame.w / 2;
|
||||
if info.ref_frame == 0 {
|
||||
if info.mode == B_PRED {
|
||||
for b in 0..16 {
|
||||
predict::predict_subblock(&mut frame.y, ys, px, py, b, bmodes[b], mb_x + 1 == self.mb_w);
|
||||
if nonzero[b] {
|
||||
let pos = (py + 4 * (b >> 2)) * ys + px + 4 * (b & 3);
|
||||
transform::add_residue(&mut frame.y, ys, pos, &transform::inverse_dct(&coeffs[b]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
predict::predict_block(&mut frame.y, ys, px, py, 16, info.mode);
|
||||
add_luma_residue(&mut frame.y, ys, px, py, &coeffs, &nonzero);
|
||||
}
|
||||
predict::predict_block(&mut frame.u, cs, px / 2, py / 2, 8, uv_mode);
|
||||
predict::predict_block(&mut frame.v, cs, px / 2, py / 2, 8, uv_mode);
|
||||
} else {
|
||||
let Some(r) = refs.get(usize::from(info.ref_frame) - 1).and_then(|r| r.as_deref()) else {
|
||||
return Err(Error::MissingReference(format!("reference frame {}", info.ref_frame)));
|
||||
};
|
||||
predict_inter_mb(r, &mut frame, mb_x, mb_y, &info, filters, full_pixel);
|
||||
add_luma_residue(&mut frame.y, ys, px, py, &coeffs, &nonzero);
|
||||
}
|
||||
for b in 16..24 {
|
||||
if nonzero[b] {
|
||||
let k = (b - 16) & 3;
|
||||
let pos = (py / 2 + 4 * (k >> 1)) * cs + px / 2 + 4 * (k & 1);
|
||||
let plane = if b < 20 { &mut frame.u } else { &mut frame.v };
|
||||
transform::add_residue(plane, cs, pos, &transform::inverse_dct(&coeffs[b]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hdr.filter_level > 0 {
|
||||
loop_filter(&mut frame, self.mb_w, self.mb_h, &lf_info, &hdr, tag.key_frame);
|
||||
}
|
||||
if let Some(s) = saved {
|
||||
self.ctx = s;
|
||||
}
|
||||
|
||||
// reference updates (9.7, 9.8): the copies first (altref, then golden from the possibly
|
||||
// updated altref), then the refreshes with the new frame
|
||||
let frame = Arc::new(frame);
|
||||
let [last, golden, altref] = self.refs.clone();
|
||||
let mut new_golden = golden;
|
||||
let mut new_altref = altref;
|
||||
match hdr.copy_to_altref {
|
||||
1 => new_altref = last.clone(),
|
||||
2 => new_altref = new_golden.clone(),
|
||||
_ => {}
|
||||
}
|
||||
match hdr.copy_to_golden {
|
||||
1 => new_golden = last.clone(),
|
||||
2 => new_golden = new_altref.clone(),
|
||||
_ => {}
|
||||
}
|
||||
if hdr.refresh_golden {
|
||||
new_golden = Some(frame.clone());
|
||||
}
|
||||
if hdr.refresh_altref {
|
||||
new_altref = Some(frame.clone());
|
||||
}
|
||||
let new_last = if hdr.refresh_last { Some(frame.clone()) } else { last };
|
||||
self.refs = [new_last, new_golden, new_altref];
|
||||
|
||||
self.stats.frames += 1;
|
||||
self.stats.key_frames += u64::from(tag.key_frame);
|
||||
if !tag.show_frame {
|
||||
self.stats.hidden_frames += 1;
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(self.picture(&frame, pts, tag.key_frame, hdr.color_space)))
|
||||
}
|
||||
|
||||
/// The visible part of a reconstructed frame.
|
||||
fn picture(&self, f: &Frame, pts: i64, key: bool, color_space: u8) -> Picture {
|
||||
let (w, h) = (self.width, self.height);
|
||||
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
|
||||
let crop = |plane: &[u8], stride: usize, w: usize, h: usize| {
|
||||
let mut out = Vec::with_capacity(w * h);
|
||||
for r in 0..h {
|
||||
out.extend_from_slice(plane.get(r * stride..r * stride + w).unwrap_or(&[]));
|
||||
}
|
||||
out
|
||||
};
|
||||
Picture {
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
chroma_width: cw as u32,
|
||||
chroma_height: ch as u32,
|
||||
y: crop(&f.y, f.w, w, h),
|
||||
u: crop(&f.u, f.w / 2, cw, ch),
|
||||
v: crop(&f.v, f.w / 2, cw, ch),
|
||||
pts,
|
||||
key,
|
||||
color_space,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loop filter level of a macroblock (15.1 with the adjustments of 9.3 and 9.4).
|
||||
fn filter_level(&self, hdr: &FrameHeader, segment: usize, info: &MbInfo) -> u8 {
|
||||
let mut level = i32::from(hdr.filter_level);
|
||||
if self.seg.enabled {
|
||||
let v = i32::from(self.seg.lf_level[segment]);
|
||||
level = if self.seg.absolute { v } else { level + v }.clamp(0, 63);
|
||||
}
|
||||
if self.lf.enabled {
|
||||
level += i32::from(self.lf.ref_deltas[usize::from(info.ref_frame)]);
|
||||
let mode_delta = match (info.ref_frame, info.mode) {
|
||||
(0, B_PRED) => Some(0),
|
||||
(0, _) => None,
|
||||
(_, MV_ZERO) => Some(1),
|
||||
(_, MV_SPLIT) => Some(3),
|
||||
_ => Some(2),
|
||||
};
|
||||
if let Some(m) = mode_delta {
|
||||
level += i32::from(self.lf.mode_deltas[m]);
|
||||
}
|
||||
level = level.clamp(0, 63);
|
||||
}
|
||||
level as u8
|
||||
}
|
||||
|
||||
/// Inter prediction record (16.2-16.4, 17) of the macroblock at `idx` in `infos`.
|
||||
fn read_inter_modes(&self, bd: &mut BoolDecoder, infos: &[MbInfo], idx: usize, stride: usize, mb_x: usize, mb_y: usize, info: &mut MbInfo) {
|
||||
let get = |i: usize| infos.get(i).copied().unwrap_or_default();
|
||||
let above = get(idx - stride);
|
||||
let left = get(idx - 1);
|
||||
let above_left = get(idx - stride - 1);
|
||||
// vp8_find_near_mvs (16.3)
|
||||
let bias = |n: &MbInfo| {
|
||||
let mut m = n.mv;
|
||||
if self.sign_bias[usize::from(n.ref_frame)] != self.sign_bias[usize::from(info.ref_frame)] {
|
||||
m.row = -m.row;
|
||||
m.col = -m.col;
|
||||
}
|
||||
m
|
||||
};
|
||||
let mut near = [Mv::ZERO; 4];
|
||||
let mut cnt = [0usize; 4];
|
||||
let mut k = 0;
|
||||
if above.ref_frame != 0 {
|
||||
if !above.mv.is_zero() {
|
||||
k += 1;
|
||||
near[k] = bias(&above);
|
||||
}
|
||||
cnt[k] += 2;
|
||||
}
|
||||
if left.ref_frame != 0 {
|
||||
if !left.mv.is_zero() {
|
||||
let m = bias(&left);
|
||||
if m != near[k] {
|
||||
k += 1;
|
||||
near[k] = m;
|
||||
}
|
||||
cnt[k] += 2;
|
||||
} else {
|
||||
cnt[0] += 2;
|
||||
}
|
||||
}
|
||||
if above_left.ref_frame != 0 {
|
||||
if !above_left.mv.is_zero() {
|
||||
let m = bias(&above_left);
|
||||
if m != near[k] {
|
||||
k += 1;
|
||||
near[k] = m;
|
||||
}
|
||||
cnt[k] += 1;
|
||||
} else {
|
||||
cnt[0] += 1;
|
||||
}
|
||||
}
|
||||
// three distinct vectors: merge the above-left one into "nearest" when equal
|
||||
if cnt[3] > 0 && near[3] == near[1] {
|
||||
cnt[1] += 1;
|
||||
}
|
||||
cnt[3] = usize::from(above.mode == MV_SPLIT) * 2 + usize::from(left.mode == MV_SPLIT) * 2 + usize::from(above_left.mode == MV_SPLIT);
|
||||
if cnt[2] > cnt[1] {
|
||||
cnt.swap(1, 2);
|
||||
near.swap(1, 2);
|
||||
}
|
||||
if cnt[1] >= cnt[0] {
|
||||
near[0] = near[1];
|
||||
}
|
||||
let bounds = MvBounds::new(mb_x, mb_y, self.mb_w, self.mb_h);
|
||||
let best = bounds.clamp(near[0]);
|
||||
let probs: [u8; 4] = std::array::from_fn(|i| MODE_CONTEXTS[cnt[i].min(5) * 4 + i]);
|
||||
info.mode = MV_NEAREST + bd.read_tree(&MV_REF_TREE, &probs);
|
||||
info.mv = match info.mode {
|
||||
MV_NEAREST => bounds.clamp(near[1]),
|
||||
MV_NEAR => bounds.clamp(near[2]),
|
||||
MV_ZERO => Mv::ZERO,
|
||||
MV_NEW => {
|
||||
let d = self.read_mv(bd);
|
||||
Mv { row: best.row + d.row, col: best.col + d.col }
|
||||
}
|
||||
_ => {
|
||||
self.read_split(bd, &left, &above, best, info);
|
||||
return;
|
||||
}
|
||||
};
|
||||
info.bmv = [info.mv; 16];
|
||||
}
|
||||
|
||||
/// SPLITMV partitioning and subblock vectors (16.4).
|
||||
fn read_split(&self, bd: &mut BoolDecoder, left: &MbInfo, above: &MbInfo, best: Mv, info: &mut MbInfo) {
|
||||
let part = usize::from(bd.read_tree(&MVPARTITION_TREE, &MVPARTITION_PROBS)) & 3;
|
||||
let map = &SPLIT_MAPS[part];
|
||||
let mut part_mv = [Mv::ZERO; 16];
|
||||
for j in 0..SPLIT_COUNTS[part] {
|
||||
// first subblock of partition j
|
||||
let first = map.iter().position(|&p| usize::from(p) == j).unwrap_or(0);
|
||||
let lmv = if first & 3 == 0 { left.bmv[first + 3] } else { part_mv[usize::from(map[first - 1])] };
|
||||
let amv = if first < 4 { above.bmv[first + 12] } else { part_mv[usize::from(map[first - 4])] };
|
||||
let context = if lmv == amv {
|
||||
if amv.is_zero() { 4 } else { 3 }
|
||||
} else if amv.is_zero() {
|
||||
2
|
||||
} else if lmv.is_zero() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mv = match bd.read_tree(&SUB_MV_REF_TREE, &SUB_MV_REF_PROBS[context * 3..context * 3 + 3]) {
|
||||
LEFT_4X4 => lmv,
|
||||
ABOVE_4X4 => amv,
|
||||
ZERO_4X4 => Mv::ZERO,
|
||||
_ => {
|
||||
let d = self.read_mv(bd);
|
||||
Mv { row: best.row + d.row, col: best.col + d.col }
|
||||
}
|
||||
};
|
||||
part_mv[j] = mv;
|
||||
}
|
||||
for (b, &p) in map.iter().enumerate() {
|
||||
info.bmv[b] = part_mv[usize::from(p)];
|
||||
}
|
||||
info.mv = info.bmv[15];
|
||||
}
|
||||
|
||||
/// `read_mv` (17.2): row, then column.
|
||||
fn read_mv(&self, bd: &mut BoolDecoder) -> Mv {
|
||||
let row = read_mv_component(bd, &self.ctx.mv[0]);
|
||||
let col = read_mv_component(bd, &self.ctx.mv[1]);
|
||||
Mv { row, col }
|
||||
}
|
||||
}
|
||||
|
||||
/// `read_mvcomponent` (17.1), in quarter pixels.
|
||||
fn read_mv_component(bd: &mut BoolDecoder, p: &[u8; 19]) -> i32 {
|
||||
let a = if bd.read_bool(p[MVP_IS_SHORT]) {
|
||||
let mut a = 0i32;
|
||||
for i in 0..3 {
|
||||
a += i32::from(bd.read_bool(p[MVP_BITS + i])) << i;
|
||||
}
|
||||
for i in (4..10).rev() {
|
||||
a += i32::from(bd.read_bool(p[MVP_BITS + i])) << i;
|
||||
}
|
||||
if a & 0xfff0 == 0 || bd.read_bool(p[MVP_BITS + 3]) {
|
||||
a += 8;
|
||||
}
|
||||
a
|
||||
} else {
|
||||
i32::from(bd.read_tree(&SMALL_MV_TREE, &p[MVP_SHORT..MVP_SHORT + 7]))
|
||||
};
|
||||
if a != 0 && bd.read_bool(p[MVP_SIGN]) { -a } else { a }
|
||||
}
|
||||
|
||||
/// The limits `vp8_clamp_mv` (16.3) keeps "nearest", "near" and "best" within: 16 pixels
|
||||
/// beyond the frame's macroblocks, in quarter pixels.
|
||||
struct MvBounds {
|
||||
min_col: i32,
|
||||
max_col: i32,
|
||||
min_row: i32,
|
||||
max_row: i32,
|
||||
}
|
||||
|
||||
impl MvBounds {
|
||||
fn new(mb_x: usize, mb_y: usize, mb_w: usize, mb_h: usize) -> MvBounds {
|
||||
const MARGIN: i32 = 16 << 2;
|
||||
MvBounds {
|
||||
min_col: -((mb_x as i32 * 16) << 2) - MARGIN,
|
||||
max_col: (((mb_w - 1 - mb_x) as i32 * 16) << 2) + MARGIN,
|
||||
min_row: -((mb_y as i32 * 16) << 2) - MARGIN,
|
||||
max_row: (((mb_h - 1 - mb_y) as i32 * 16) << 2) + MARGIN,
|
||||
}
|
||||
}
|
||||
fn clamp(&self, m: Mv) -> Mv {
|
||||
Mv { row: m.row.clamp(self.min_row, self.max_row), col: m.col.clamp(self.min_col, self.max_col) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Subblock mode a 16x16 luma mode stands for in B_PRED contexts (11.3).
|
||||
fn implied_bmode(mode: u8) -> u8 {
|
||||
match mode {
|
||||
V_PRED => B_VE_PRED,
|
||||
H_PRED => B_HE_PRED,
|
||||
TM_PRED => B_TM_PRED,
|
||||
_ => B_DC_PRED,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the tokens of one block (13), storing dequantized coefficients in raster order.
|
||||
/// Returns the position after the last token (where the end of block was read, or 16).
|
||||
fn read_coefficients(bd: &mut BoolDecoder, probs: &[[[u8; 11]; 3]; 8], ctx: usize, first: usize, dq: [i32; 2], out: &mut [i16; 16]) -> usize {
|
||||
let mut ctx = ctx.min(2);
|
||||
let mut prev_zero = false;
|
||||
let mut i = first;
|
||||
while i < 16 {
|
||||
let p = &probs[usize::from(COEFF_BANDS[i])][ctx];
|
||||
let token = if prev_zero { bd.read_tree_from(&COEFF_TREE, p, 2) } else { bd.read_tree(&COEFF_TREE, p) };
|
||||
if token == DCT_EOB {
|
||||
break;
|
||||
}
|
||||
let abs = match token {
|
||||
DCT_0..=DCT_4 => i32::from(token),
|
||||
_ => {
|
||||
let cat = usize::from(token - DCT_CAT1).min(5);
|
||||
let mut extra = 0i32;
|
||||
for &pb in PCAT[cat] {
|
||||
extra = extra + extra + i32::from(bd.read_bool(pb));
|
||||
}
|
||||
CAT_BASE[cat] + extra
|
||||
}
|
||||
};
|
||||
let v = if abs != 0 && bd.read_bool(128) { -abs } else { abs };
|
||||
ctx = match abs {
|
||||
0 => 0,
|
||||
1 => 1,
|
||||
_ => 2,
|
||||
};
|
||||
prev_zero = token == DCT_0;
|
||||
out[ZIGZAG[i]] = (v * dq[usize::from(i > 0)]) as i16;
|
||||
i += 1;
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
/// Add the residue of the 16 Y subblocks of the macroblock at `(px, py)`.
|
||||
fn add_luma_residue(plane: &mut [u8], stride: usize, px: usize, py: usize, coeffs: &[[i16; 16]; 25], nonzero: &[bool; 25]) {
|
||||
for b in 0..16 {
|
||||
if nonzero[b] {
|
||||
let pos = (py + 4 * (b >> 2)) * stride + px + 4 * (b & 3);
|
||||
transform::add_residue(plane, stride, pos, &transform::inverse_dct(&coeffs[b]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inter prediction of a macroblock (18) into `frame`.
|
||||
fn predict_inter_mb(r: &Frame, frame: &mut Frame, mb_x: usize, mb_y: usize, info: &MbInfo, filters: &[i32; 48], full_pixel: bool) {
|
||||
let (px, py) = (mb_x * 16, mb_y * 16);
|
||||
let ys = frame.w;
|
||||
let cs = frame.w / 2;
|
||||
let ry = RefPlane { data: &r.y, stride: r.w, width: r.w, height: r.h };
|
||||
let ru = RefPlane { data: &r.u, stride: r.w / 2, width: r.w / 2, height: r.h / 2 };
|
||||
let rv = RefPlane { data: &r.v, stride: r.w / 2, width: r.w / 2, height: r.h / 2 };
|
||||
let fp = |v: i32| if full_pixel { v & !7 } else { v };
|
||||
if info.mode != MV_SPLIT {
|
||||
let m = info.mv;
|
||||
predict::predict_inter(&ry, &mut frame.y, ys, px, py, 16, 16, m.col * 2, m.row * 2, filters);
|
||||
// chroma: the luma vector in quarter pixels is the chroma vector in eighth pixels
|
||||
let (cx, cy) = (fp(m.col), fp(m.row));
|
||||
predict::predict_inter(&ru, &mut frame.u, cs, px / 2, py / 2, 8, 8, cx, cy, filters);
|
||||
predict::predict_inter(&rv, &mut frame.v, cs, px / 2, py / 2, 8, 8, cx, cy, filters);
|
||||
return;
|
||||
}
|
||||
for b in 0..16 {
|
||||
let m = info.bmv[b];
|
||||
predict::predict_inter(&ry, &mut frame.y, ys, px + 4 * (b & 3), py + 4 * (b >> 2), 4, 4, m.col * 2, m.row * 2, filters);
|
||||
}
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
let k = 8 * i + 2 * j;
|
||||
let sum = |f: fn(Mv) -> i32| [k, k + 1, k + 4, k + 5].iter().map(|&b| f(info.bmv[b])).sum::<i32>();
|
||||
// average of the four luma vectors, in eighth chroma pixels (18.1)
|
||||
let avg = |s: i32| (s + 2 + if s < 0 { -1 } else { 0 }) >> 2;
|
||||
let cx = fp(avg(sum(|m| m.col)));
|
||||
let cy = fp(avg(sum(|m| m.row)));
|
||||
let (bx, by) = (px / 2 + 4 * j, py / 2 + 4 * i);
|
||||
predict::predict_inter(&ru, &mut frame.u, cs, bx, by, 4, 4, cx, cy, filters);
|
||||
predict::predict_inter(&rv, &mut frame.v, cs, bx, by, 4, 4, cx, cy, filters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loop filter of the whole frame (15.1), macroblocks in raster order.
|
||||
fn loop_filter(frame: &mut Frame, mb_w: usize, mb_h: usize, lf_info: &[(u8, bool)], hdr: &FrameHeader, key_frame: bool) {
|
||||
let ys = frame.w;
|
||||
let cs = frame.w / 2;
|
||||
let simple = hdr.filter_type == 1;
|
||||
for mb_y in 0..mb_h {
|
||||
for mb_x in 0..mb_w {
|
||||
let Some(&(level, inner)) = lf_info.get(mb_y * mb_w + mb_x) else { continue };
|
||||
if level == 0 {
|
||||
continue;
|
||||
}
|
||||
let prm = Params::new(level, hdr.sharpness, key_frame);
|
||||
let y0 = mb_y * 16 * ys + mb_x * 16;
|
||||
let c0 = mb_y * 8 * cs + mb_x * 8;
|
||||
if simple {
|
||||
if mb_x > 0 {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, true, Kind::Simple, &prm);
|
||||
}
|
||||
if inner {
|
||||
for x in [4, 8, 12] {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + x, true, Kind::SimpleInner, &prm);
|
||||
}
|
||||
}
|
||||
if mb_y > 0 {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, false, Kind::Simple, &prm);
|
||||
}
|
||||
if inner {
|
||||
for y in [4, 8, 12] {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + y * ys, false, Kind::SimpleInner, &prm);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if mb_x > 0 {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, true, Kind::NormalMb, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.u, cs, c0, true, Kind::NormalMb, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.v, cs, c0, true, Kind::NormalMb, &prm);
|
||||
}
|
||||
if inner {
|
||||
for x in [4, 8, 12] {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + x, true, Kind::NormalSub, &prm);
|
||||
}
|
||||
loopfilter::filter_edge::<8>(&mut frame.u, cs, c0 + 4, true, Kind::NormalSub, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.v, cs, c0 + 4, true, Kind::NormalSub, &prm);
|
||||
}
|
||||
if mb_y > 0 {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, false, Kind::NormalMb, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.u, cs, c0, false, Kind::NormalMb, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.v, cs, c0, false, Kind::NormalMb, &prm);
|
||||
}
|
||||
if inner {
|
||||
for y in [4, 8, 12] {
|
||||
loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + y * ys, false, Kind::NormalSub, &prm);
|
||||
}
|
||||
loopfilter::filter_edge::<8>(&mut frame.u, cs, c0 + 4 * cs, false, Kind::NormalSub, &prm);
|
||||
loopfilter::filter_edge::<8>(&mut frame.v, cs, c0 + 4 * cs, false, Kind::NormalSub, &prm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/// Errors produced by the decoder.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// A syntax element had a value outside its legal range, or a size did not fit the data.
|
||||
#[error("invalid bitstream: {0}")]
|
||||
Invalid(String),
|
||||
/// An inter frame came before any key frame (decoding must start at a key frame).
|
||||
#[error("missing reference frame: {0}")]
|
||||
MissingReference(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// `ensure!(cond, "message")` returns `Error::Invalid` when `cond` is false.
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $($arg:tt)+) => {
|
||||
if !$cond {
|
||||
return Err($crate::error::Error::Invalid(format!($($arg)+)));
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use ensure;
|
||||
@@ -0,0 +1,268 @@
|
||||
//! Frame tag (9.1, 19.1) and the frame header in the first partition (9.2-9.11, 19.2).
|
||||
|
||||
use crate::boolcoder::BoolDecoder;
|
||||
use crate::error::{Error, Result, ensure};
|
||||
|
||||
/// The uncompressed data chunk at the start of every frame (9.1).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct FrameTag {
|
||||
pub key_frame: bool,
|
||||
/// Bitstream version (0-3): reconstruction filter and full-pixel chroma.
|
||||
pub version: u8,
|
||||
pub show_frame: bool,
|
||||
pub first_part_size: usize,
|
||||
/// Key frames only: dimensions and upscaling codes.
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub horizontal_scale: u8,
|
||||
pub vertical_scale: u8,
|
||||
/// Bytes of the uncompressed chunk (3, or 10 for key frames).
|
||||
pub header_len: usize,
|
||||
}
|
||||
|
||||
impl FrameTag {
|
||||
pub fn parse(data: &[u8]) -> Result<FrameTag> {
|
||||
let [b0, b1, b2, ..] = *data else {
|
||||
return Err(Error::Invalid("frame shorter than its 3-byte tag".into()));
|
||||
};
|
||||
let tmp = u32::from(b0) | u32::from(b1) << 8 | u32::from(b2) << 16;
|
||||
let key_frame = tmp & 1 == 0;
|
||||
let mut tag = FrameTag {
|
||||
key_frame,
|
||||
version: ((tmp >> 1) & 7) as u8,
|
||||
show_frame: (tmp >> 4) & 1 == 1,
|
||||
first_part_size: (tmp >> 5) as usize,
|
||||
width: 0,
|
||||
height: 0,
|
||||
horizontal_scale: 0,
|
||||
vertical_scale: 0,
|
||||
header_len: 3,
|
||||
};
|
||||
if key_frame {
|
||||
let Some(c) = data.get(3..10) else {
|
||||
return Err(Error::Invalid("key frame shorter than its 10-byte header".into()));
|
||||
};
|
||||
ensure!(c[0] == 0x9d && c[1] == 0x01 && c[2] == 0x2a, "bad key frame start code");
|
||||
let w = u16::from_le_bytes([c[3], c[4]]);
|
||||
let h = u16::from_le_bytes([c[5], c[6]]);
|
||||
tag.width = w & 0x3fff;
|
||||
tag.horizontal_scale = (w >> 14) as u8;
|
||||
tag.height = h & 0x3fff;
|
||||
tag.vertical_scale = (h >> 14) as u8;
|
||||
tag.header_len = 10;
|
||||
}
|
||||
Ok(tag)
|
||||
}
|
||||
}
|
||||
|
||||
/// Segment-based adjustments (9.3): carried from frame to frame.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Segmentation {
|
||||
pub enabled: bool,
|
||||
pub update_map: bool,
|
||||
/// Feature values are absolute (else deltas from the frame values).
|
||||
pub absolute: bool,
|
||||
pub quant: [i8; 4],
|
||||
pub lf_level: [i8; 4],
|
||||
pub tree_probs: [u8; 3],
|
||||
}
|
||||
|
||||
/// Loop filter adjustments by reference frame and mode (9.4): carried from frame to frame.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct LfDeltas {
|
||||
pub enabled: bool,
|
||||
/// Intra, last, golden, altref.
|
||||
pub ref_deltas: [i8; 4],
|
||||
/// B_PRED, ZEROMV, other MV modes, SPLITMV.
|
||||
pub mode_deltas: [i8; 4],
|
||||
}
|
||||
|
||||
/// Probabilities that persist across frames (until the next key frame), saved and restored
|
||||
/// around a frame whose `refresh_entropy_probs` is 0.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EntropyContext {
|
||||
pub coeff: [[[[u8; 11]; 3]; 8]; 4],
|
||||
pub ymode: [u8; 4],
|
||||
pub uv_mode: [u8; 3],
|
||||
pub mv: [[u8; 19]; 2],
|
||||
}
|
||||
|
||||
impl Default for EntropyContext {
|
||||
fn default() -> Self {
|
||||
use crate::spec_tables::*;
|
||||
let mut coeff = [[[[0u8; 11]; 3]; 8]; 4];
|
||||
for (i, p) in coeff.iter_mut().flatten().flatten().flatten().enumerate() {
|
||||
*p = DEFAULT_COEFF_PROBS[i];
|
||||
}
|
||||
let mut mv = [[0u8; 19]; 2];
|
||||
for (i, p) in mv.iter_mut().flatten().enumerate() {
|
||||
*p = DEFAULT_MV_PROBS[i];
|
||||
}
|
||||
EntropyContext { coeff, ymode: YMODE_PROBS, uv_mode: UV_MODE_PROBS, mv }
|
||||
}
|
||||
}
|
||||
|
||||
/// Dequantization indices (9.6).
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct QuantIndices {
|
||||
pub y_ac: i32,
|
||||
pub y_dc_delta: i32,
|
||||
pub y2_dc_delta: i32,
|
||||
pub y2_ac_delta: i32,
|
||||
pub uv_dc_delta: i32,
|
||||
pub uv_ac_delta: i32,
|
||||
}
|
||||
|
||||
/// Per-frame header fields (9.2-9.11) that do not persist.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct FrameHeader {
|
||||
pub color_space: u8,
|
||||
pub clamping_type: u8,
|
||||
/// 0 normal, 1 simple.
|
||||
pub filter_type: u8,
|
||||
pub filter_level: u8,
|
||||
pub sharpness: u8,
|
||||
pub partitions: usize,
|
||||
pub quant: QuantIndices,
|
||||
pub refresh_golden: bool,
|
||||
pub refresh_altref: bool,
|
||||
/// 0 none, 1 last, 2 altref.
|
||||
pub copy_to_golden: u8,
|
||||
/// 0 none, 1 last, 2 golden.
|
||||
pub copy_to_altref: u8,
|
||||
pub sign_bias_golden: bool,
|
||||
pub sign_bias_altref: bool,
|
||||
pub refresh_entropy: bool,
|
||||
pub refresh_last: bool,
|
||||
/// `prob_skip_false` when `mb_no_skip_coeff` is set.
|
||||
pub skip_prob: Option<u8>,
|
||||
pub prob_intra: u8,
|
||||
pub prob_last: u8,
|
||||
pub prob_golden: u8,
|
||||
}
|
||||
|
||||
/// Read the frame header from the first partition, updating the persistent state.
|
||||
/// `saved` receives the entropy context to restore after the frame when
|
||||
/// `refresh_entropy_probs` is 0.
|
||||
pub fn read_header(
|
||||
d: &mut BoolDecoder,
|
||||
key_frame: bool,
|
||||
seg: &mut Segmentation,
|
||||
lf: &mut LfDeltas,
|
||||
ctx: &mut EntropyContext,
|
||||
saved: &mut Option<EntropyContext>,
|
||||
) -> FrameHeader {
|
||||
let mut h = FrameHeader::default();
|
||||
if key_frame {
|
||||
h.color_space = d.read_literal(1) as u8;
|
||||
h.clamping_type = d.read_literal(1) as u8;
|
||||
}
|
||||
seg.enabled = d.read_flag();
|
||||
seg.update_map = false;
|
||||
if seg.enabled {
|
||||
seg.update_map = d.read_flag();
|
||||
let update_data = d.read_flag();
|
||||
if update_data {
|
||||
seg.absolute = d.read_flag();
|
||||
for q in &mut seg.quant {
|
||||
*q = d.read_optional_signed(7) as i8;
|
||||
}
|
||||
for l in &mut seg.lf_level {
|
||||
*l = d.read_optional_signed(6) as i8;
|
||||
}
|
||||
}
|
||||
if seg.update_map {
|
||||
for p in &mut seg.tree_probs {
|
||||
*p = if d.read_flag() { d.read_literal(8) as u8 } else { crate::tables::DEFAULT_SEGMENT_PROB };
|
||||
}
|
||||
}
|
||||
}
|
||||
h.filter_type = d.read_literal(1) as u8;
|
||||
h.filter_level = d.read_literal(6) as u8;
|
||||
h.sharpness = d.read_literal(3) as u8;
|
||||
lf.enabled = d.read_flag();
|
||||
if lf.enabled && d.read_flag() {
|
||||
for v in &mut lf.ref_deltas {
|
||||
if d.read_flag() {
|
||||
*v = d.read_signed(6) as i8;
|
||||
}
|
||||
}
|
||||
for v in &mut lf.mode_deltas {
|
||||
if d.read_flag() {
|
||||
*v = d.read_signed(6) as i8;
|
||||
}
|
||||
}
|
||||
}
|
||||
h.partitions = 1 << d.read_literal(2);
|
||||
h.quant = QuantIndices {
|
||||
y_ac: d.read_literal(7) as i32,
|
||||
y_dc_delta: d.read_optional_signed(4),
|
||||
y2_dc_delta: d.read_optional_signed(4),
|
||||
y2_ac_delta: d.read_optional_signed(4),
|
||||
uv_dc_delta: d.read_optional_signed(4),
|
||||
uv_ac_delta: d.read_optional_signed(4),
|
||||
};
|
||||
if key_frame {
|
||||
h.refresh_golden = true;
|
||||
h.refresh_altref = true;
|
||||
h.refresh_entropy = d.read_flag();
|
||||
h.refresh_last = true;
|
||||
} else {
|
||||
h.refresh_golden = d.read_flag();
|
||||
h.refresh_altref = d.read_flag();
|
||||
if !h.refresh_golden {
|
||||
h.copy_to_golden = d.read_literal(2) as u8;
|
||||
}
|
||||
if !h.refresh_altref {
|
||||
h.copy_to_altref = d.read_literal(2) as u8;
|
||||
}
|
||||
h.sign_bias_golden = d.read_flag();
|
||||
h.sign_bias_altref = d.read_flag();
|
||||
h.refresh_entropy = d.read_flag();
|
||||
h.refresh_last = d.read_flag();
|
||||
}
|
||||
// The probabilities in effect before this frame's updates come back after it.
|
||||
*saved = if h.refresh_entropy { None } else { Some(ctx.clone()) };
|
||||
// token_prob_update (13.4)
|
||||
let mut i = 0;
|
||||
for plane in ctx.coeff.iter_mut() {
|
||||
for band in plane.iter_mut() {
|
||||
for c in band.iter_mut() {
|
||||
for p in c.iter_mut() {
|
||||
if d.read_bool(crate::spec_tables::COEFF_UPDATE_PROBS[i]) {
|
||||
*p = d.read_literal(8) as u8;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h.skip_prob = if d.read_flag() { Some(d.read_literal(8) as u8) } else { None };
|
||||
if !key_frame {
|
||||
h.prob_intra = d.read_literal(8) as u8;
|
||||
h.prob_last = d.read_literal(8) as u8;
|
||||
h.prob_golden = d.read_literal(8) as u8;
|
||||
if d.read_flag() {
|
||||
for p in &mut ctx.ymode {
|
||||
*p = d.read_literal(8) as u8;
|
||||
}
|
||||
}
|
||||
if d.read_flag() {
|
||||
for p in &mut ctx.uv_mode {
|
||||
*p = d.read_literal(8) as u8;
|
||||
}
|
||||
}
|
||||
// mv_prob_update (17.2)
|
||||
let mut i = 0;
|
||||
for comp in ctx.mv.iter_mut() {
|
||||
for p in comp.iter_mut() {
|
||||
if d.read_bool(crate::spec_tables::MV_UPDATE_PROBS[i]) {
|
||||
let x = d.read_literal(7) as u8;
|
||||
*p = if x != 0 { x << 1 } else { 1 };
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
h
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Clean-room pure-Rust VP8 decoder, implemented from RFC 6386 ("VP8 Data Format and Decoding
|
||||
//! Guide"), sections 1-19.
|
||||
//!
|
||||
//! 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
|
||||
//! sign bias, NEAREST / NEAR / ZERO / NEW / SPLIT motion vectors, six-tap and bilinear
|
||||
//! interpolation (versions 0-3, full-pixel chroma), segmentation, the normal and simple loop
|
||||
//! filters with mode / reference deltas, multiple token partitions, probability persistence
|
||||
//! (`refresh_entropy_probs`), golden / altref copies and hidden (altref) frames.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! let mut dec = filmcraft_vp8::Decoder::new();
|
||||
//! let frame: Vec<u8> = Vec::new(); // one IVF / WebM frame
|
||||
//! if let Some(pic) = dec.decode(&frame, 0).unwrap() {
|
||||
//! println!("{}x{}", pic.width, pic.height);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
|
||||
|
||||
mod boolcoder;
|
||||
mod decoder;
|
||||
mod error;
|
||||
mod header;
|
||||
mod loopfilter;
|
||||
mod predict;
|
||||
#[rustfmt::skip]
|
||||
mod spec_tables;
|
||||
mod tables;
|
||||
mod transform;
|
||||
|
||||
pub use decoder::{DecodeStats, Decoder};
|
||||
pub use error::{Error, Result};
|
||||
|
||||
/// A decoded picture: 8-bit 4:2:0, planes tight (stride = width).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Picture {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub chroma_width: u32,
|
||||
pub chroma_height: u32,
|
||||
pub y: Vec<u8>,
|
||||
pub u: Vec<u8>,
|
||||
pub v: Vec<u8>,
|
||||
/// `pts` passed to [`Decoder::decode`] with the frame.
|
||||
pub pts: i64,
|
||||
pub key: bool,
|
||||
/// `color_space` of the last key frame (0: BT.601 YUV, 1: reserved).
|
||||
pub color_space: u8,
|
||||
}
|
||||
|
||||
/// Stream parameters read from a key frame's uncompressed chunk (see [`keyframe_info`]).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct KeyframeInfo {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
/// Upscaling codes (9.1), informational only.
|
||||
pub horizontal_scale: u8,
|
||||
pub vertical_scale: u8,
|
||||
pub version: u8,
|
||||
}
|
||||
|
||||
/// 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()?;
|
||||
(tag.key_frame && tag.width > 0 && tag.height > 0).then_some(KeyframeInfo {
|
||||
width: u32::from(tag.width),
|
||||
height: u32::from(tag.height),
|
||||
horizontal_scale: tag.horizontal_scale,
|
||||
vertical_scale: tag.vertical_scale,
|
||||
version: tag.version,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether `frame` is a key frame (decoding can start there).
|
||||
pub fn is_keyframe(frame: &[u8]) -> bool {
|
||||
keyframe_info(frame).is_some()
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//! Loop filter (15).
|
||||
|
||||
/// Filter parameters of one macroblock (15.4).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Params {
|
||||
pub mbedge_limit: i32,
|
||||
pub sub_bedge_limit: i32,
|
||||
pub interior_limit: i32,
|
||||
pub hev_threshold: i32,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
pub fn new(level: u8, sharpness: u8, key_frame: bool) -> Params {
|
||||
let level = i32::from(level);
|
||||
let mut interior = level;
|
||||
if sharpness > 0 {
|
||||
interior >>= if sharpness > 4 { 2 } else { 1 };
|
||||
interior = interior.min(9 - i32::from(sharpness));
|
||||
}
|
||||
let interior = interior.max(1);
|
||||
let hev_threshold = match (key_frame, level) {
|
||||
(_, l) if l >= 40 => 2 + i32::from(!key_frame),
|
||||
(false, l) if l >= 20 => 2,
|
||||
(_, l) if l >= 15 => 1,
|
||||
_ => 0,
|
||||
};
|
||||
Params { mbedge_limit: (level + 2) * 2 + interior, sub_bedge_limit: level * 2 + interior, interior_limit: interior, hev_threshold }
|
||||
}
|
||||
}
|
||||
|
||||
/// Which edge filter to apply along an edge.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Kind {
|
||||
/// The simple filter (15.2) with the macroblock edge limit.
|
||||
Simple,
|
||||
/// The simple filter with the subblock edge limit.
|
||||
SimpleInner,
|
||||
/// The normal macroblock edge filter (`MBfilter`, 15.3).
|
||||
NormalMb,
|
||||
/// The normal subblock edge filter (`subblock_filter`, 15.3).
|
||||
NormalSub,
|
||||
}
|
||||
|
||||
/// Filter `N` segments at once: `px[k][l]` is pixel `k - 4` across the edge of segment `l`
|
||||
/// (p3 p2 p1 p0 | q0 q1 q2 q3). Branch-free: the conditions of 15.2 / 15.3 become masks
|
||||
/// (all ones or zero) that zero the adjustment where the filter does not apply, which gives the
|
||||
/// same values as the specification's procedures; each step is a plain loop over the lanes, which
|
||||
/// the compiler vectorizes.
|
||||
#[inline(always)]
|
||||
fn filter_lanes<const N: usize, const NORMAL: bool, const MB: bool>(px: &mut [[u8; N]; 8], edge_limit: i16, prm: &Params) {
|
||||
let (lim, thr) = (prm.interior_limit as i16, prm.hev_threshold as i16);
|
||||
let ld = |k: usize| -> [i16; N] { std::array::from_fn(|l| i16::from(px[k][l])) };
|
||||
let (p3, p2, p1, p0, q0, q1, q2, q3) = (ld(0), ld(1), ld(2), ld(3), ld(4), ld(5), ld(6), ld(7));
|
||||
let mut m = [0i16; N];
|
||||
let mut hev = [0i16; N];
|
||||
for l in 0..N {
|
||||
let edge = (p0[l] - q0[l]).abs() * 2 + (p1[l] - q1[l]).abs() / 2;
|
||||
m[l] = -i16::from(edge <= edge_limit);
|
||||
}
|
||||
if NORMAL {
|
||||
for l in 0..N {
|
||||
let d = (p3[l] - p2[l])
|
||||
.abs()
|
||||
.max((p2[l] - p1[l]).abs())
|
||||
.max((p1[l] - p0[l]).abs())
|
||||
.max((q3[l] - q2[l]).abs())
|
||||
.max((q2[l] - q1[l]).abs())
|
||||
.max((q1[l] - q0[l]).abs());
|
||||
m[l] &= -i16::from(d <= lim);
|
||||
hev[l] = -i16::from((p1[l] - p0[l]).abs().max((q1[l] - q0[l]).abs()) > thr);
|
||||
}
|
||||
}
|
||||
let c = |v: i16| v.clamp(-128, 127);
|
||||
let out = |v: i16| (c(v) + 128) as u8;
|
||||
for l in 0..N {
|
||||
let (sp2, sp1, sp0, sq0, sq1, sq2) = (p2[l] - 128, p1[l] - 128, p0[l] - 128, q0[l] - 128, q1[l] - 128, q2[l] - 128);
|
||||
if !NORMAL {
|
||||
// simple_segment / common_adjust with the outer taps
|
||||
let f = c(c(sp1 - sq1) + 3 * (sq0 - sp0)) & m[l];
|
||||
px[4][l] = out(sq0 - (c(f + 4) >> 3));
|
||||
px[3][l] = out(sp0 + (c(f + 3) >> 3));
|
||||
} else if !MB {
|
||||
// subblock_filter: common_adjust (outer taps on high edge variance), then half of it
|
||||
// on p1 / q1 without high edge variance
|
||||
let f = c((c(sp1 - sq1) & hev[l]) + 3 * (sq0 - sp0)) & m[l];
|
||||
let f1 = c(f + 4) >> 3;
|
||||
px[4][l] = out(sq0 - f1);
|
||||
px[3][l] = out(sp0 + (c(f + 3) >> 3));
|
||||
let a = ((f1 + 1) >> 1) & !hev[l];
|
||||
px[5][l] = out(sq1 - a);
|
||||
px[2][l] = out(sp1 + a);
|
||||
} else {
|
||||
// MBfilter: common_adjust with outer taps on high edge variance, the 27/18/9
|
||||
// adjustments otherwise
|
||||
let w = c(c(sp1 - sq1) + 3 * (sq0 - sp0)) & m[l];
|
||||
let f = w & hev[l];
|
||||
let q0a = c(sq0 - (c(f + 4) >> 3));
|
||||
let p0a = c(sp0 + (c(f + 3) >> 3));
|
||||
let w = w & !hev[l];
|
||||
// 27 * w + 63 fits 16 bits: |w| <= 128
|
||||
let a = |mul: i16| c((mul * w + 63) >> 7);
|
||||
let (a27, a18, a9) = (a(27), a(18), a(9));
|
||||
px[4][l] = out(q0a - a27);
|
||||
px[3][l] = out(p0a + a27);
|
||||
px[5][l] = out(sq1 - a18);
|
||||
px[2][l] = out(sp1 + a18);
|
||||
px[6][l] = out(sq2 - a9);
|
||||
px[1][l] = out(sp2 + a9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter an edge (15.2, 15.3) of `N` segments. `start` is q0 of the first segment; a vertical
|
||||
/// edge (`vertical`) has its segments one row apart, a horizontal one one pixel apart. Every
|
||||
/// segment must have 4 pixels inside the plane on both sides of the edge; an edge that does not
|
||||
/// is left alone.
|
||||
pub fn filter_edge<const N: usize>(plane: &mut [u8], stride: usize, start: usize, vertical: bool, kind: Kind, prm: &Params) {
|
||||
let (step, along) = if vertical { (1, stride) } else { (stride, 1) };
|
||||
if start < 4 * step || start + (N - 1) * along + 3 * step >= plane.len() {
|
||||
return;
|
||||
}
|
||||
let mut px = [[0u8; N]; 8];
|
||||
let first = start - 4 * step;
|
||||
if vertical {
|
||||
for l in 0..N {
|
||||
let row = first + l * stride;
|
||||
for k in 0..8 {
|
||||
px[k][l] = plane[row + k];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (k, row) in px.iter_mut().enumerate() {
|
||||
let at = first + k * stride;
|
||||
row.copy_from_slice(&plane[at..at + N]);
|
||||
}
|
||||
}
|
||||
match kind {
|
||||
Kind::Simple => filter_lanes::<N, false, false>(&mut px, prm.mbedge_limit as i16, prm),
|
||||
Kind::SimpleInner => filter_lanes::<N, false, false>(&mut px, prm.sub_bedge_limit as i16, prm),
|
||||
Kind::NormalMb => filter_lanes::<N, true, true>(&mut px, prm.mbedge_limit as i16, prm),
|
||||
Kind::NormalSub => filter_lanes::<N, true, false>(&mut px, prm.sub_bedge_limit as i16, prm),
|
||||
}
|
||||
if vertical {
|
||||
for l in 0..N {
|
||||
let row = first + l * stride;
|
||||
for k in 1..7 {
|
||||
plane[row + k] = px[k][l];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (k, row) in px.iter().enumerate().take(7).skip(1) {
|
||||
let at = first + k * stride;
|
||||
plane[at..at + N].copy_from_slice(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
//! Intra prediction (12) and inter prediction (18).
|
||||
//!
|
||||
//! Planes are the decoder's reconstruction buffers, whose size is a whole number of macroblocks.
|
||||
//! Intra prediction reads the not yet loop-filtered current frame; pixels above the frame are 127
|
||||
//! and pixels left of it 129 (the corner above-left of a row's first macroblock is 127 on the top
|
||||
//! row and 129 below it). Inter prediction reads a reference plane extended infinitely by
|
||||
//! repeating its edge pixels.
|
||||
|
||||
use crate::tables::*;
|
||||
|
||||
/// A pixel of the current frame for intra prediction, with the out-of-frame values of section 12.
|
||||
#[inline]
|
||||
fn edge(plane: &[u8], stride: usize, x: isize, y: isize) -> u8 {
|
||||
if y < 0 {
|
||||
127
|
||||
} else if x < 0 {
|
||||
129
|
||||
} else {
|
||||
plane.get(y as usize * stride + x as usize).copied().unwrap_or(127)
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict a whole `size`x`size` block (16x16 luma or 8x8 chroma) at `(x0, y0)` with a
|
||||
/// macroblock-level mode (12.2, 12.3), writing the prediction into the plane.
|
||||
pub fn predict_block(plane: &mut [u8], stride: usize, x0: usize, y0: usize, size: usize, mode: u8) {
|
||||
let (xi, yi) = (x0 as isize, y0 as isize);
|
||||
let mut above = [0u8; 16];
|
||||
let mut left = [0u8; 16];
|
||||
for i in 0..size {
|
||||
above[i] = edge(plane, stride, xi + i as isize, yi - 1);
|
||||
left[i] = edge(plane, stride, xi - 1, yi + i as isize);
|
||||
}
|
||||
let p = i32::from(edge(plane, stride, xi - 1, yi - 1));
|
||||
let shift = size.trailing_zeros();
|
||||
let fill = |plane: &mut [u8], f: &dyn Fn(usize, usize) -> u8| {
|
||||
for r in 0..size {
|
||||
let start = (y0 + r) * stride + x0;
|
||||
if let Some(row) = plane.get_mut(start..start + size) {
|
||||
for (c, px) in row.iter_mut().enumerate() {
|
||||
*px = f(r, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
match mode {
|
||||
V_PRED => fill(plane, &|_, c| above[c]),
|
||||
H_PRED => fill(plane, &|r, _| left[r]),
|
||||
TM_PRED => fill(plane, &|r, c| (i32::from(left[r]) + i32::from(above[c]) - p).clamp(0, 255) as u8),
|
||||
_ => {
|
||||
// DC_PRED: only the genuinely available edges count
|
||||
let sum = |v: &[u8]| v[..size].iter().map(|&x| u32::from(x)).sum::<u32>();
|
||||
let dc = match (y0 > 0, x0 > 0) {
|
||||
(true, true) => (sum(&above) + sum(&left) + (1 << shift)) >> (shift + 1),
|
||||
(true, false) => (sum(&above) + (1 << (shift - 1))) >> shift,
|
||||
(false, true) => (sum(&left) + (1 << (shift - 1))) >> shift,
|
||||
(false, false) => 128,
|
||||
} as u8;
|
||||
fill(plane, &|_, _| dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict the 4x4 luma subblock `b` (raster index in its macroblock) of the macroblock at
|
||||
/// `(mx, my)` (in pixels) with subblock mode `mode` (12.3). `last_column`: the macroblock is the
|
||||
/// rightmost of its row.
|
||||
pub fn predict_subblock(plane: &mut [u8], stride: usize, mx: usize, my: usize, b: usize, mode: u8, last_column: bool) {
|
||||
let (bx, by) = (b & 3, b >> 2);
|
||||
let x = (mx + 4 * bx) as isize;
|
||||
let y = (my + 4 * by) as isize;
|
||||
// A[-1] = P, A[0..8]: the 4 pixels above and the 4 above-right
|
||||
let mut a = [0i32; 9];
|
||||
a[0] = i32::from(edge(plane, stride, x - 1, y - 1));
|
||||
for i in 0..4 {
|
||||
a[1 + i] = i32::from(edge(plane, stride, x + i as isize, y - 1));
|
||||
}
|
||||
for i in 0..4 {
|
||||
a[5 + i] = i32::from(if bx < 3 {
|
||||
edge(plane, stride, x + 4 + i as isize, y - 1)
|
||||
} else if my == 0 {
|
||||
127
|
||||
} else if last_column {
|
||||
// the macroblock's above-right pixels lie outside the frame: repeat (-1, 15)
|
||||
edge(plane, stride, mx as isize + 15, my as isize - 1)
|
||||
} else {
|
||||
// right-column subblocks all use the pixels above-right of the macroblock
|
||||
edge(plane, stride, mx as isize + 16 + i as isize, my as isize - 1)
|
||||
});
|
||||
}
|
||||
let mut l = [0i32; 4];
|
||||
for (i, v) in l.iter_mut().enumerate() {
|
||||
*v = i32::from(edge(plane, stride, x - 1, y + i as isize));
|
||||
}
|
||||
let av = |i: isize| a[(i + 1) as usize]; // A[i], i in -1..8
|
||||
// E[0..9] = L[3], L[2], L[1], L[0], P, A[0..4]
|
||||
let e = [l[3], l[2], l[1], l[0], a[0], a[1], a[2], a[3], a[4]];
|
||||
let avg3 = |x: i32, y: i32, z: i32| (x + y + y + z + 2) >> 2;
|
||||
let avg2 = |x: i32, y: i32| (x + y + 1) >> 1;
|
||||
let a3 = |i: isize| avg3(av(i - 1), av(i), av(i + 1)); // avg3p(A + i)
|
||||
let a2 = |i: isize| avg2(av(i), av(i + 1)); // avg2p(A + i)
|
||||
let e3 = |i: usize| avg3(e[i - 1], e[i], e[i + 1]);
|
||||
let e2 = |i: usize| avg2(e[i], e[i + 1]);
|
||||
// L[-1] = P
|
||||
let lv = |i: isize| if i < 0 { a[0] } else { l[i as usize] };
|
||||
let l3 = |i: isize| avg3(lv(i - 1), lv(i), lv(i + 1));
|
||||
let l2 = |i: isize| avg2(lv(i), lv(i + 1));
|
||||
let mut out = [[0i32; 4]; 4];
|
||||
match mode {
|
||||
B_TM_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
out[r][c] = (l[r] + av(c as isize) - a[0]).clamp(0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
B_VE_PRED => {
|
||||
for c in 0..4 {
|
||||
let v = a3(c as isize);
|
||||
for row in &mut out {
|
||||
row[c] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
B_HE_PRED => {
|
||||
for r in 0..4 {
|
||||
let v = if r == 3 { avg3(l[2], l[3], l[3]) } else { l3(r as isize) };
|
||||
out[r] = [v; 4];
|
||||
}
|
||||
}
|
||||
B_LD_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
let d = r + c;
|
||||
out[r][c] = if d == 6 { avg3(av(6), av(7), av(7)) } else { a3(d as isize + 1) };
|
||||
}
|
||||
}
|
||||
}
|
||||
B_RD_PRED => {
|
||||
for r in 0..4 {
|
||||
for c in 0..4 {
|
||||
// (3,0) uses E[1], each step towards the upper right one further
|
||||
out[r][c] = e3((4 + c as isize - r as isize) as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
B_VR_PRED => {
|
||||
out[3][0] = e3(2);
|
||||
out[2][0] = e3(3);
|
||||
out[3][1] = e3(4);
|
||||
out[1][0] = e3(4);
|
||||
out[2][1] = e2(4);
|
||||
out[0][0] = e2(4);
|
||||
out[3][2] = e3(5);
|
||||
out[1][1] = e3(5);
|
||||
out[2][2] = e2(5);
|
||||
out[0][1] = e2(5);
|
||||
out[3][3] = e3(6);
|
||||
out[1][2] = e3(6);
|
||||
out[2][3] = e2(6);
|
||||
out[0][2] = e2(6);
|
||||
out[1][3] = e3(7);
|
||||
out[0][3] = e2(7);
|
||||
}
|
||||
B_VL_PRED => {
|
||||
out[0][0] = a2(0);
|
||||
out[1][0] = a3(1);
|
||||
out[2][0] = a2(1);
|
||||
out[0][1] = a2(1);
|
||||
out[1][1] = a3(2);
|
||||
out[3][0] = a3(2);
|
||||
out[2][1] = a2(2);
|
||||
out[0][2] = a2(2);
|
||||
out[3][1] = a3(3);
|
||||
out[1][2] = a3(3);
|
||||
out[2][2] = a2(3);
|
||||
out[0][3] = a2(3);
|
||||
out[3][2] = a3(4);
|
||||
out[1][3] = a3(4);
|
||||
out[2][3] = a3(5);
|
||||
out[3][3] = a3(6);
|
||||
}
|
||||
B_HD_PRED => {
|
||||
out[3][0] = e2(0);
|
||||
out[3][1] = e3(1);
|
||||
out[2][0] = e2(1);
|
||||
out[3][2] = e2(1);
|
||||
out[2][1] = e3(2);
|
||||
out[3][3] = e3(2);
|
||||
out[2][2] = e2(2);
|
||||
out[1][0] = e2(2);
|
||||
out[2][3] = e3(3);
|
||||
out[1][1] = e3(3);
|
||||
out[1][2] = e2(3);
|
||||
out[0][0] = e2(3);
|
||||
out[1][3] = e3(4);
|
||||
out[0][1] = e3(4);
|
||||
out[0][2] = e3(5);
|
||||
out[0][3] = e3(6);
|
||||
}
|
||||
B_HU_PRED => {
|
||||
out[0][0] = l2(0);
|
||||
out[0][1] = l3(1);
|
||||
out[0][2] = l2(1);
|
||||
out[1][0] = l2(1);
|
||||
out[0][3] = l3(2);
|
||||
out[1][1] = l3(2);
|
||||
out[1][2] = l2(2);
|
||||
out[2][0] = l2(2);
|
||||
out[1][3] = avg3(l[2], l[3], l[3]);
|
||||
out[2][1] = avg3(l[2], l[3], l[3]);
|
||||
out[2][2] = l[3];
|
||||
out[2][3] = l[3];
|
||||
out[3] = [l[3]; 4];
|
||||
}
|
||||
_ => {
|
||||
// B_DC_PRED
|
||||
let v = (4 + (1..5).map(|i| a[i]).sum::<i32>() + l.iter().sum::<i32>()) >> 3;
|
||||
out = [[v; 4]; 4];
|
||||
}
|
||||
}
|
||||
for (r, row) in out.iter().enumerate() {
|
||||
let start = (y as usize + r) * stride + x as usize;
|
||||
if let Some(dst) = plane.get_mut(start..start + 4) {
|
||||
for (d, &v) in dst.iter_mut().zip(row) {
|
||||
*d = v as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference plane for inter prediction: reads outside it repeat the nearest edge pixel.
|
||||
pub struct RefPlane<'a> {
|
||||
pub data: &'a [u8],
|
||||
pub stride: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl RefPlane<'_> {
|
||||
#[inline]
|
||||
fn at(&self, x: isize, y: isize) -> u8 {
|
||||
let x = x.clamp(0, self.width as isize - 1) as usize;
|
||||
let y = y.clamp(0, self.height as isize - 1) as usize;
|
||||
self.data.get(y * self.stride + x).copied().unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict the `w`x`h` block at `(bx, by)` of a plane from `reference` displaced by the motion
|
||||
/// vector `(mvx, mvy)` in eighth pixels, using `filters` (8 x 6 taps: the six-tap or bilinear
|
||||
/// set, 18.3), writing into `dst` (same layout as the block's plane).
|
||||
pub fn predict_inter(
|
||||
reference: &RefPlane,
|
||||
dst: &mut [u8],
|
||||
dst_stride: usize,
|
||||
bx: usize,
|
||||
by: usize,
|
||||
w: usize,
|
||||
h: usize,
|
||||
mvx: i32,
|
||||
mvy: i32,
|
||||
filters: &[i32; 48],
|
||||
) {
|
||||
let x0 = bx as isize + (mvx >> 3) as isize;
|
||||
let y0 = by as isize + (mvy >> 3) as isize;
|
||||
let (fx, fy) = ((mvx & 7) as usize, (mvy & 7) as usize);
|
||||
// source window: 2 before, 3 after in each direction
|
||||
const MAX: usize = 16 + 5;
|
||||
let mut src = [[0u8; MAX]; MAX];
|
||||
let full_inside = x0 >= 2 && y0 >= 2 && x0 + w as isize + 3 <= reference.width as isize && y0 + h as isize + 3 <= reference.height as isize;
|
||||
for r in 0..h + 5 {
|
||||
let yy = y0 + r as isize - 2;
|
||||
if full_inside {
|
||||
let start = yy as usize * reference.stride + x0 as usize - 2;
|
||||
if let Some(row) = reference.data.get(start..start + w + 5) {
|
||||
src[r][..w + 5].copy_from_slice(row);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for c in 0..w + 5 {
|
||||
src[r][c] = reference.at(x0 + c as isize - 2, yy);
|
||||
}
|
||||
}
|
||||
if fx == 0 && fy == 0 {
|
||||
for r in 0..h {
|
||||
let start = (by + r) * dst_stride + bx;
|
||||
if let Some(row) = dst.get_mut(start..start + w) {
|
||||
row.copy_from_slice(&src[r + 2][2..2 + w]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let tap6 = |f: usize| -> [i32; 6] { std::array::from_fn(|k| filters.get(f * 6 + k).copied().unwrap_or(0)) };
|
||||
let (hf, vf) = (tap6(fx), tap6(fy));
|
||||
let round = |v: i32| ((v + 64) >> 7).clamp(0, 255);
|
||||
// horizontal pass over the h + 5 rows (an identity when fx is 0), then vertical
|
||||
let mut tmp = [[0i32; 16]; MAX];
|
||||
for r in 0..h + 5 {
|
||||
let s = &src[r];
|
||||
let t = &mut tmp[r];
|
||||
if fx == 0 {
|
||||
for c in 0..w {
|
||||
t[c] = i32::from(s[c + 2]);
|
||||
}
|
||||
} else {
|
||||
for c in 0..w {
|
||||
let v = i32::from(s[c]) * hf[0]
|
||||
+ i32::from(s[c + 1]) * hf[1]
|
||||
+ i32::from(s[c + 2]) * hf[2]
|
||||
+ i32::from(s[c + 3]) * hf[3]
|
||||
+ i32::from(s[c + 4]) * hf[4]
|
||||
+ i32::from(s[c + 5]) * hf[5];
|
||||
t[c] = round(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
for r in 0..h {
|
||||
let start = (by + r) * dst_stride + bx;
|
||||
let Some(row) = dst.get_mut(start..start + w) else { continue };
|
||||
if fy == 0 {
|
||||
for (c, d) in row.iter_mut().enumerate() {
|
||||
*d = tmp[r + 2][c] as u8;
|
||||
}
|
||||
} else {
|
||||
for (c, d) in row.iter_mut().enumerate() {
|
||||
let v =
|
||||
tmp[r][c] * vf[0] + tmp[r + 1][c] * vf[1] + tmp[r + 2][c] * vf[2] + tmp[r + 3][c] * vf[3] + tmp[r + 4][c] * vf[4] + tmp[r + 5][c] * vf[5];
|
||||
*d = round(v) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// Generated by tools/extract_tables.py from the text of RFC 6386 (sections 1-19). Do not edit.
|
||||
// Every table is flat, row-major, in the order the specification prints it.
|
||||
|
||||
/// `coeff_update_probs` (RFC 6386), 1056 entries.
|
||||
pub const COEFF_UPDATE_PROBS: [u8; 1056] = [
|
||||
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,
|
||||
];
|
||||
|
||||
/// `default_coeff_probs` (RFC 6386), 1056 entries.
|
||||
pub const DEFAULT_COEFF_PROBS: [u8; 1056] = [
|
||||
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,
|
||||
];
|
||||
|
||||
/// `kf_bmode_prob` (RFC 6386), 900 entries.
|
||||
pub const KF_BMODE_PROBS: [u8; 900] = [
|
||||
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,
|
||||
];
|
||||
|
||||
/// `dc_qlookup` (RFC 6386), 128 entries.
|
||||
pub const DC_QLOOKUP: [u16; 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_qlookup` (RFC 6386), 128 entries.
|
||||
pub const AC_QLOOKUP: [u16; 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,
|
||||
];
|
||||
|
||||
/// `vp8_mode_contexts` (RFC 6386), 24 entries.
|
||||
pub const MODE_CONTEXTS: [u8; 24] = [
|
||||
7, 1, 1, 143, 14, 18, 14, 107, 135, 64, 57, 68, 60, 56, 128, 65,
|
||||
159, 134, 128, 34, 234, 188, 128, 28,
|
||||
];
|
||||
|
||||
/// `sub_mv_ref_prob` (RFC 6386), 15 entries.
|
||||
pub const SUB_MV_REF_PROBS: [u8; 15] = [
|
||||
147, 136, 18, 106, 145, 1, 179, 121, 1, 223, 1, 34, 208, 1, 1,
|
||||
];
|
||||
|
||||
/// `vp8_mv_update_probs` (RFC 6386), 38 entries.
|
||||
pub const MV_UPDATE_PROBS: [u8; 38] = [
|
||||
237, 246, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 250, 250,
|
||||
252, 254, 254, 231, 243, 245, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254,
|
||||
254, 251, 251, 254, 254, 254,
|
||||
];
|
||||
|
||||
/// `default_mv_context` (RFC 6386), 38 entries.
|
||||
pub const DEFAULT_MV_PROBS: [u8; 38] = [
|
||||
162, 128, 225, 146, 172, 147, 214, 39, 156, 128, 129, 132, 75, 145, 178, 206,
|
||||
239, 254, 254, 164, 128, 204, 170, 119, 235, 140, 230, 228, 128, 130, 130, 74,
|
||||
148, 180, 203, 236, 254, 254,
|
||||
];
|
||||
|
||||
/// `bmode_prob` (RFC 6386), 9 entries.
|
||||
pub const BMODE_PROBS: [u8; 9] = [
|
||||
120, 90, 79, 133, 87, 85, 80, 111, 151,
|
||||
];
|
||||
|
||||
/// `coeff_bands` (RFC 6386), 16 entries.
|
||||
pub const COEFF_BANDS: [u8; 16] = [
|
||||
0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7,
|
||||
];
|
||||
|
||||
/// `kf_ymode_prob` (RFC 6386), 4 entries.
|
||||
pub const KF_YMODE_PROBS: [u8; 4] = [
|
||||
145, 156, 163, 128,
|
||||
];
|
||||
|
||||
/// `kf_uv_mode_prob` (RFC 6386), 3 entries.
|
||||
pub const KF_UV_MODE_PROBS: [u8; 3] = [
|
||||
142, 114, 183,
|
||||
];
|
||||
|
||||
/// `ymode_prob` (RFC 6386), 4 entries.
|
||||
pub const YMODE_PROBS: [u8; 4] = [
|
||||
112, 86, 140, 37,
|
||||
];
|
||||
|
||||
/// `uv_mode_prob` (RFC 6386), 3 entries.
|
||||
pub const UV_MODE_PROBS: [u8; 3] = [
|
||||
162, 101, 204,
|
||||
];
|
||||
|
||||
/// `mvpartition_probs` (RFC 6386), 3 entries.
|
||||
pub const MVPARTITION_PROBS: [u8; 3] = [
|
||||
110, 111, 150,
|
||||
];
|
||||
|
||||
/// `BilinearFilters` (RFC 6386), 48 entries.
|
||||
pub const BILINEAR_FILTERS: [i32; 48] = [
|
||||
0, 0, 128, 0, 0, 0, 0, 0, 112, 16, 0, 0, 0, 0, 96, 32,
|
||||
0, 0, 0, 0, 80, 48, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0,
|
||||
48, 80, 0, 0, 0, 0, 32, 96, 0, 0, 0, 0, 16, 112, 0, 0,
|
||||
];
|
||||
|
||||
/// `filters` (RFC 6386), 48 entries.
|
||||
pub const SIXTAP_FILTERS: [i32; 48] = [
|
||||
0, 0, 128, 0, 0, 0, 0, -6, 123, 12, -1, 0, 2, -11, 108, 36,
|
||||
-8, 1, 0, -9, 93, 50, -6, 0, 3, -16, 77, 77, -16, 3, 0, -6,
|
||||
50, 93, -9, 0, 1, -8, 36, 108, -11, 2, 0, -1, 12, 123, -6, 0,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
//! Trees, enumerations and small tables of RFC 6386 that are not printed as `= { ... }` arrays
|
||||
//! (the large tables are in `spec_tables.rs`).
|
||||
|
||||
// Macroblock luma / chroma modes (8.2), in the specification's enumeration order.
|
||||
pub const DC_PRED: u8 = 0;
|
||||
pub const V_PRED: u8 = 1;
|
||||
pub const H_PRED: u8 = 2;
|
||||
pub const TM_PRED: u8 = 3;
|
||||
pub const B_PRED: u8 = 4;
|
||||
|
||||
// Subblock modes (11.2).
|
||||
pub const B_DC_PRED: u8 = 0;
|
||||
pub const B_TM_PRED: u8 = 1;
|
||||
pub const B_VE_PRED: u8 = 2;
|
||||
pub const B_HE_PRED: u8 = 3;
|
||||
pub const B_LD_PRED: u8 = 4;
|
||||
pub const B_RD_PRED: u8 = 5;
|
||||
pub const B_VR_PRED: u8 = 6;
|
||||
pub const B_VL_PRED: u8 = 7;
|
||||
pub const B_HD_PRED: u8 = 8;
|
||||
pub const B_HU_PRED: u8 = 9;
|
||||
|
||||
// Inter modes (16.2), numbered after the intra modes as in the specification.
|
||||
pub const MV_NEAREST: u8 = 5;
|
||||
pub const MV_NEAR: u8 = 6;
|
||||
pub const MV_ZERO: u8 = 7;
|
||||
pub const MV_NEW: u8 = 8;
|
||||
pub const MV_SPLIT: u8 = 9;
|
||||
|
||||
// Split MV subblock modes (16.4), relative numbering.
|
||||
pub const LEFT_4X4: u8 = 0;
|
||||
pub const ABOVE_4X4: u8 = 1;
|
||||
pub const ZERO_4X4: u8 = 2;
|
||||
pub const NEW_4X4: u8 = 3;
|
||||
|
||||
/// `ymode_tree` (8.2).
|
||||
pub const YMODE_TREE: [i8; 8] = [-(DC_PRED as i8), 2, 4, 6, -(V_PRED as i8), -(H_PRED as i8), -(TM_PRED as i8), -(B_PRED as i8)];
|
||||
/// `kf_ymode_tree` (8.2).
|
||||
pub const KF_YMODE_TREE: [i8; 8] = [-(B_PRED as i8), 2, 4, 6, -(DC_PRED as i8), -(V_PRED as i8), -(H_PRED as i8), -(TM_PRED as i8)];
|
||||
/// `uv_mode_tree` (8.2).
|
||||
pub const UV_MODE_TREE: [i8; 6] = [-(DC_PRED as i8), 2, -(V_PRED as i8), 4, -(H_PRED as i8), -(TM_PRED as i8)];
|
||||
/// `bmode_tree` (11.2).
|
||||
pub const BMODE_TREE: [i8; 18] = [
|
||||
-(B_DC_PRED as i8),
|
||||
2,
|
||||
-(B_TM_PRED as i8),
|
||||
4,
|
||||
-(B_VE_PRED as i8),
|
||||
6,
|
||||
8,
|
||||
12,
|
||||
-(B_HE_PRED as i8),
|
||||
10,
|
||||
-(B_RD_PRED as i8),
|
||||
-(B_VR_PRED as i8),
|
||||
-(B_LD_PRED as i8),
|
||||
14,
|
||||
-(B_VL_PRED as i8),
|
||||
16,
|
||||
-(B_HD_PRED as i8),
|
||||
-(B_HU_PRED as i8),
|
||||
];
|
||||
/// `mb_segment_tree` (10).
|
||||
pub const SEGMENT_TREE: [i8; 6] = [2, 4, 0, -1, -2, -3];
|
||||
/// `mv_ref_tree` (16.2), values relative to `MV_NEAREST`.
|
||||
pub const MV_REF_TREE: [i8; 8] =
|
||||
[-((MV_ZERO - MV_NEAREST) as i8), 2, 0, 4, -((MV_NEAR - MV_NEAREST) as i8), 6, -((MV_NEW - MV_NEAREST) as i8), -((MV_SPLIT - MV_NEAREST) as i8)];
|
||||
|
||||
// Split partitionings (16.4).
|
||||
pub const SPLIT_TOP_BOTTOM: u8 = 0;
|
||||
pub const SPLIT_LEFT_RIGHT: u8 = 1;
|
||||
pub const SPLIT_QUARTERS: u8 = 2;
|
||||
pub const SPLIT_16: u8 = 3;
|
||||
/// `mvpartition_tree` (16.4).
|
||||
pub const MVPARTITION_TREE: [i8; 6] = [-(SPLIT_16 as i8), 2, -(SPLIT_QUARTERS as i8), 4, -(SPLIT_TOP_BOTTOM as i8), -(SPLIT_LEFT_RIGHT as i8)];
|
||||
/// `sub_mv_ref_tree` (16.4).
|
||||
pub const SUB_MV_REF_TREE: [i8; 6] = [-(LEFT_4X4 as i8), 2, -(ABOVE_4X4 as i8), 4, -(ZERO_4X4 as i8), -(NEW_4X4 as i8)];
|
||||
/// The partition of each Y subblock, per partitioning (the layouts listed with the MVpartition
|
||||
/// enumeration, 16.4).
|
||||
pub const SPLIT_MAPS: [[u8; 16]; 4] = [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
];
|
||||
/// Number of partitions per partitioning.
|
||||
pub const SPLIT_COUNTS: [usize; 4] = [2, 2, 4, 16];
|
||||
|
||||
/// `small_mvtree` (17.1).
|
||||
pub const SMALL_MV_TREE: [i8; 14] = [2, 8, 4, 6, 0, -1, -2, -3, 10, 12, -4, -5, -6, -7];
|
||||
// Offsets in an MV_CONTEXT (17.1).
|
||||
pub const MVP_IS_SHORT: usize = 0;
|
||||
pub const MVP_SIGN: usize = 1;
|
||||
pub const MVP_SHORT: usize = 2;
|
||||
pub const MVP_BITS: usize = 9;
|
||||
|
||||
// DCT tokens (13.2).
|
||||
pub const DCT_0: u8 = 0;
|
||||
pub const DCT_4: u8 = 4;
|
||||
pub const DCT_CAT1: u8 = 5;
|
||||
pub const DCT_EOB: u8 = 11;
|
||||
/// `coeff_tree` (13.2).
|
||||
pub const COEFF_TREE: [i8; 22] = [-(DCT_EOB as i8), 2, -(DCT_0 as i8), 4, -1, 6, 8, 12, -2, 10, -3, -4, 14, 16, -5, -6, 18, 20, -7, -8, -9, -10];
|
||||
/// `Pcat1` ... `Pcat6` (13.2), without the terminating zeros.
|
||||
pub 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]];
|
||||
/// `categoryBase` (13.3).
|
||||
pub const CAT_BASE: [i32; 6] = [5, 7, 11, 19, 35, 67];
|
||||
|
||||
/// Coefficient scan order (13): position `i` of the token stream is coefficient
|
||||
/// `ZIGZAG[i]` of the 4x4 block in raster order — the usual 4x4 zig-zag.
|
||||
pub const ZIGZAG: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
|
||||
|
||||
/// Probabilities of the segment tree when not updated (9.3).
|
||||
pub const DEFAULT_SEGMENT_PROB: u8 = 255;
|
||||
@@ -0,0 +1,94 @@
|
||||
//! Inverse transforms (14.3, 14.4). Inputs, the intermediate signal and the outputs are 16-bit
|
||||
//! signed integers, as the specification requires; the casts to `i16` reproduce that.
|
||||
|
||||
const COSPI8SQRT2MINUS1: i32 = 20091;
|
||||
const SINPI8SQRT2: i32 = 35468;
|
||||
|
||||
/// Inverse WHT of the Y2 block (14.3): `input` in raster order; `out[i]` becomes the DC of
|
||||
/// Y subblock `i`.
|
||||
pub fn inverse_wht(input: &[i16; 16]) -> [i16; 16] {
|
||||
let mut tmp = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let ip = |k: usize| i32::from(input[i + 4 * k]);
|
||||
let a1 = ip(0) + ip(3);
|
||||
let b1 = ip(1) + ip(2);
|
||||
let c1 = ip(1) - ip(2);
|
||||
let d1 = ip(0) - ip(3);
|
||||
tmp[i] = (a1 + b1) as i16;
|
||||
tmp[i + 4] = (c1 + d1) as i16;
|
||||
tmp[i + 8] = (a1 - b1) as i16;
|
||||
tmp[i + 12] = (d1 - c1) as i16;
|
||||
}
|
||||
let mut out = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let ip = |k: usize| i32::from(tmp[4 * i + k]);
|
||||
let a1 = ip(0) + ip(3);
|
||||
let b1 = ip(1) + ip(2);
|
||||
let c1 = ip(1) - ip(2);
|
||||
let d1 = ip(0) - ip(3);
|
||||
out[4 * i] = ((a1 + b1 + 3) >> 3) as i16;
|
||||
out[4 * i + 1] = ((c1 + d1 + 3) >> 3) as i16;
|
||||
out[4 * i + 2] = ((a1 - b1 + 3) >> 3) as i16;
|
||||
out[4 * i + 3] = ((d1 - c1 + 3) >> 3) as i16;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Inverse DCT (14.4) of a 4x4 block in raster order, returning the residue in raster order.
|
||||
pub fn inverse_dct(input: &[i16; 16]) -> [i16; 16] {
|
||||
let mul_s = |x: i32| (x * SINPI8SQRT2) >> 16;
|
||||
let mul_c = |x: i32| x + ((x * COSPI8SQRT2MINUS1) >> 16);
|
||||
let mut tmp = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let ip = |k: usize| i32::from(input[i + 4 * k]);
|
||||
let a1 = ip(0) + ip(2);
|
||||
let b1 = ip(0) - ip(2);
|
||||
let c1 = mul_s(ip(1)) - mul_c(ip(3));
|
||||
let d1 = mul_c(ip(1)) + mul_s(ip(3));
|
||||
tmp[i] = (a1 + d1) as i16;
|
||||
tmp[i + 12] = (a1 - d1) as i16;
|
||||
tmp[i + 4] = (b1 + c1) as i16;
|
||||
tmp[i + 8] = (b1 - c1) as i16;
|
||||
}
|
||||
let mut out = [0i16; 16];
|
||||
for i in 0..4 {
|
||||
let ip = |k: usize| i32::from(tmp[4 * i + k]);
|
||||
let a1 = ip(0) + ip(2);
|
||||
let b1 = ip(0) - ip(2);
|
||||
let c1 = mul_s(ip(1)) - mul_c(ip(3));
|
||||
let d1 = mul_c(ip(1)) + mul_s(ip(3));
|
||||
out[4 * i] = ((a1 + d1 + 4) >> 3) as i16;
|
||||
out[4 * i + 3] = ((a1 - d1 + 4) >> 3) as i16;
|
||||
out[4 * i + 1] = ((b1 + c1 + 4) >> 3) as i16;
|
||||
out[4 * i + 2] = ((b1 - c1 + 4) >> 3) as i16;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Add `residue` to the 4x4 block at `pos` of a plane (14.5), saturating to 8 bits.
|
||||
#[inline]
|
||||
pub fn add_residue(plane: &mut [u8], stride: usize, pos: usize, residue: &[i16; 16]) {
|
||||
for r in 0..4 {
|
||||
let Some(row) = plane.get_mut(pos + r * stride..pos + r * stride + 4) else { return };
|
||||
for (c, px) in row.iter_mut().enumerate() {
|
||||
*px = (i32::from(*px) + i32::from(residue[4 * r + c])).clamp(0, 255) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dc_only_blocks_are_flat() {
|
||||
for dc in [-2048i16, -100, -5, -4, -3, 0, 3, 4, 5, 100, 2047] {
|
||||
let mut b = [0i16; 16];
|
||||
b[0] = dc;
|
||||
let out = inverse_dct(&b);
|
||||
assert!(out.iter().all(|&v| i32::from(v) == (i32::from(dc) + 4) >> 3), "dct dc {dc}");
|
||||
let out = inverse_wht(&b);
|
||||
assert!(out.iter().all(|&v| i32::from(v) == (i32::from(dc) + 3) >> 3), "wht dc {dc}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//! Test fixture generation (ffmpeg + libvpx as external encoder and oracle), a tiny IVF reader
|
||||
//! and the decode helpers.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use filmcraft_vp8::{Decoder, Picture};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// One encoder configuration of the fixture matrix.
|
||||
pub struct Fixture {
|
||||
pub name: &'static str,
|
||||
/// lavfi source (without size / rate).
|
||||
pub source: &'static str,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub frames: u32,
|
||||
/// libvpx arguments; a leading "2pass" encodes in two passes.
|
||||
pub args: &'static [&'static str],
|
||||
}
|
||||
|
||||
macro_rules! fx {
|
||||
($name:expr, $src:expr, $w:expr, $h:expr, $n:expr, [$($a:expr),* $(,)?]) => {
|
||||
Fixture { name: $name, source: $src, width: $w, height: $h, frames: $n, args: &[$($a),*] }
|
||||
};
|
||||
}
|
||||
|
||||
pub const FIXTURES: &[Fixture] = &[
|
||||
fx!("default", "testsrc2", 352, 288, 20, ["-b:v", "600k"]),
|
||||
fx!("odd_size", "testsrc2", 177, 99, 12, ["-b:v", "300k"]),
|
||||
fx!("tiny", "testsrc2", 33, 17, 10, ["-b:v", "50k"]),
|
||||
fx!("intra_only", "mandelbrot", 352, 288, 6, ["-g", "1", "-b:v", "2M"]),
|
||||
fx!("noise_high_rate", "cellauto", 352, 288, 10, ["-b:v", "4M"]),
|
||||
fx!("near_lossless", "testsrc2", 176, 144, 8, ["-qmin", "0", "-qmax", "0", "-b:v", "20M"]),
|
||||
fx!("coarse", "mandelbrot", 352, 288, 12, ["-qmin", "60", "-qmax", "63", "-crf", "62", "-b:v", "100k"]),
|
||||
fx!("partitions8", "testsrc2", 640, 360, 8, ["-b:v", "1M", "-slices", "8"]),
|
||||
fx!("altref_2pass", "mandelbrot", 352, 288, 30, ["2pass", "-b:v", "500k", "-auto-alt-ref", "1", "-lag-in-frames", "16"]),
|
||||
fx!("sharpness7", "testsrc2", 352, 288, 10, ["-b:v", "300k", "-sharpness", "7"]),
|
||||
fx!("version1_bilinear_simple", "testsrc2", 352, 288, 12, ["-b:v", "500k", "-profile:v", "1"]),
|
||||
fx!("version2", "testsrc2", 352, 288, 12, ["-b:v", "500k", "-profile:v", "2"]),
|
||||
fx!("version3_fullpixel", "mandelbrot", 352, 288, 12, ["-b:v", "500k", "-profile:v", "3"]),
|
||||
fx!("realtime_segments", "testsrc2", 352, 288, 20, ["-b:v", "300k", "-deadline", "realtime", "-cpu-used", "8", "-error-resilient", "1"]),
|
||||
fx!("screen", "smptehdbars", 352, 288, 12, ["-b:v", "200k", "-screen-content-mode", "1"]),
|
||||
fx!(
|
||||
"temporal_layers",
|
||||
"testsrc2",
|
||||
352,
|
||||
288,
|
||||
20,
|
||||
[
|
||||
"-b:v",
|
||||
"800k",
|
||||
"-deadline",
|
||||
"realtime",
|
||||
"-cpu-used",
|
||||
"8",
|
||||
// error resilient: frames keep their probability updates to themselves, so the
|
||||
// enhancement layer can be dropped
|
||||
"-error-resilient",
|
||||
"1",
|
||||
"-ts-parameters",
|
||||
"ts_number_layers=2:ts_target_bitrate=400,800:ts_rate_decimator=2,1:ts_periodicity=2:ts_layer_id=0,1:ts_layering_mode=2"
|
||||
]
|
||||
),
|
||||
fx!("hd_1080p", "testsrc2", 1920, 1080, 3, ["-b:v", "4M", "-deadline", "realtime", "-cpu-used", "8"]),
|
||||
];
|
||||
|
||||
pub fn fixture(name: &str) -> &'static Fixture {
|
||||
FIXTURES.iter().find(|f| f.name == name).unwrap_or_else(|| panic!("unknown fixture {name}"))
|
||||
}
|
||||
|
||||
pub fn ffmpeg() -> Option<PathBuf> {
|
||||
for p in ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/usr/bin/ffmpeg"] {
|
||||
if Path::new(p).exists() {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn fixtures_dir() -> PathBuf {
|
||||
let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/fixtures/vp8-tests");
|
||||
std::fs::create_dir_all(&d).expect("create fixtures dir");
|
||||
d
|
||||
}
|
||||
|
||||
/// Unique suffix for temporary files: tests run on several threads and may generate the same
|
||||
/// fixture concurrently.
|
||||
pub fn unique() -> String {
|
||||
static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
format!("{}-{n}", std::process::id())
|
||||
}
|
||||
|
||||
pub fn run(cmd: &mut Command) -> bool {
|
||||
match cmd.output() {
|
||||
Ok(o) if o.status.success() => true,
|
||||
Ok(o) => {
|
||||
eprintln!("command failed: {cmd:?}\n{}", String::from_utf8_lossy(&o.stderr));
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("failed to run {cmd:?}: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate (if needed) the fixture stream and its reference decode by libvpx through ffmpeg.
|
||||
/// `None` (the test skips) without ffmpeg or its libvpx encoder.
|
||||
pub fn ensure(f: &Fixture) -> Option<(PathBuf, PathBuf)> {
|
||||
// one generation at a time: tests share fixtures, and realtime libvpx encodes differ from run
|
||||
// to run, so a stream and its reference decode must come from the same encode
|
||||
static GENERATING: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
let _guard = GENERATING.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let Some(ff) = ffmpeg() else {
|
||||
eprintln!("SKIP {}: ffmpeg not found", f.name);
|
||||
return None;
|
||||
};
|
||||
let dir = fixtures_dir();
|
||||
let ivf = dir.join(format!("{}.ivf", f.name));
|
||||
let yuv = dir.join(format!("{}.yuv", f.name));
|
||||
if !ivf.exists() {
|
||||
let tmp = dir.join(format!("{}.tmp.{}.ivf", f.name, unique()));
|
||||
// the source at an even size, scaled to the fixture's (possibly odd) size
|
||||
let src =
|
||||
format!("{}=size={}x{}:rate=25,scale={}:{},format=yuv420p", f.source, f.width.next_multiple_of(2), f.height.next_multiple_of(2), f.width, f.height);
|
||||
let two_pass = f.args.first() == Some(&"2pass");
|
||||
let args: Vec<&str> = f.args.iter().copied().filter(|a| *a != "2pass").collect();
|
||||
let log = dir.join(format!("{}.{}.passlog", f.name, unique()));
|
||||
let enc = |pass: Option<u32>| {
|
||||
let mut c = Command::new(&ff);
|
||||
c.args(["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", &src]);
|
||||
c.args(["-frames:v", &f.frames.to_string(), "-c:v", "libvpx"]);
|
||||
c.args(&args);
|
||||
if let Some(p) = pass {
|
||||
c.args(["-pass", &p.to_string(), "-passlogfile"]).arg(&log);
|
||||
}
|
||||
if pass == Some(1) {
|
||||
c.args(["-f", "null", "-"]);
|
||||
} else {
|
||||
c.args(["-f", "ivf"]).arg(&tmp);
|
||||
}
|
||||
c
|
||||
};
|
||||
if two_pass && !run(&mut enc(Some(1))) {
|
||||
eprintln!("SKIP {}: encode failed (ffmpeg without libvpx?)", f.name);
|
||||
return None;
|
||||
}
|
||||
if !run(&mut enc(two_pass.then_some(2))) {
|
||||
eprintln!("SKIP {}: encode failed (ffmpeg without libvpx?)", f.name);
|
||||
return None;
|
||||
}
|
||||
if std::fs::rename(&tmp, &ivf).is_err() && !ivf.exists() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if !yuv.exists() {
|
||||
let tmp = dir.join(format!("{}.tmp.{}.yuv", f.name, unique()));
|
||||
let mut c = Command::new(&ff);
|
||||
c.args(["-hide_banner", "-loglevel", "error", "-y", "-c:v", "libvpx", "-i"]).arg(&ivf);
|
||||
c.args(["-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", "yuv420p"]).arg(&tmp);
|
||||
if !run(&mut c) {
|
||||
eprintln!("SKIP {}: reference decode failed", f.name);
|
||||
return None;
|
||||
}
|
||||
if std::fs::rename(&tmp, &yuv).is_err() && !yuv.exists() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some((ivf, yuv))
|
||||
}
|
||||
|
||||
/// Frames of an IVF file.
|
||||
pub fn ivf_frames(data: &[u8]) -> Vec<&[u8]> {
|
||||
let mut out = Vec::new();
|
||||
let Some(h) = data.get(6..8) else { return out };
|
||||
let mut pos = usize::from(u16::from_le_bytes([h[0], h[1]]));
|
||||
while pos + 12 <= data.len() {
|
||||
let size = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
|
||||
pos += 12;
|
||||
let end = (pos + size).min(data.len());
|
||||
out.push(&data[pos..end]);
|
||||
pos = end;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Decode every frame; the pictures in display order.
|
||||
pub fn decode_all(frames: &[&[u8]]) -> Vec<Picture> {
|
||||
let mut dec = Decoder::new();
|
||||
frames.iter().enumerate().filter_map(|(i, f)| dec.decode(f, i as i64).expect("decode")).collect()
|
||||
}
|
||||
|
||||
/// A picture as I420 bytes.
|
||||
pub fn i420(p: &Picture) -> Vec<u8> {
|
||||
[p.y.as_slice(), &p.u, &p.v].concat()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Bit-exactness against libvpx (through ffmpeg) on generated fixtures covering every coding
|
||||
//! tool: skipped when ffmpeg or its libvpx encoder is absent.
|
||||
|
||||
mod common;
|
||||
|
||||
fn check(name: &str) {
|
||||
let f = common::fixture(name);
|
||||
let Some((ivf, yuv)) = common::ensure(f) else { return };
|
||||
let data = std::fs::read(&ivf).unwrap();
|
||||
let reference = std::fs::read(&yuv).unwrap();
|
||||
let pictures = common::decode_all(&common::ivf_frames(&data));
|
||||
let (w, h) = (f.width as usize, f.height as usize);
|
||||
let size = w * h + 2 * w.div_ceil(2) * h.div_ceil(2);
|
||||
assert_eq!(pictures.len(), reference.len() / size, "{name}: picture count");
|
||||
for (i, p) in pictures.iter().enumerate() {
|
||||
assert_eq!((p.width as usize, p.height as usize), (w, h), "{name}: size of picture {i}");
|
||||
assert!(common::i420(p) == reference[i * size..(i + 1) * size], "{name}: picture {i} differs from libvpx");
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! fixture_tests {
|
||||
($($name:ident),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check(stringify!($name));
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
fixture_tests!(
|
||||
default,
|
||||
odd_size,
|
||||
tiny,
|
||||
intra_only,
|
||||
noise_high_rate,
|
||||
near_lossless,
|
||||
coarse,
|
||||
partitions8,
|
||||
altref_2pass,
|
||||
sharpness7,
|
||||
version1_bilinear_simple,
|
||||
version2,
|
||||
version3_fullpixel,
|
||||
realtime_segments,
|
||||
screen,
|
||||
temporal_layers,
|
||||
hd_1080p,
|
||||
);
|
||||
|
||||
/// Hidden (altref) frames produce no picture, and leaving out the frames the decoder calls
|
||||
/// disposable changes none of the other pictures.
|
||||
#[test]
|
||||
fn hidden_and_disposable_frames() {
|
||||
for name in ["altref_2pass", "temporal_layers"] {
|
||||
let f = common::fixture(name);
|
||||
let Some((ivf, _)) = common::ensure(f) else { return };
|
||||
let data = std::fs::read(&ivf).unwrap();
|
||||
let frames = common::ivf_frames(&data);
|
||||
// every picture, and which frames could be left out (asked right before each is decoded)
|
||||
let mut dec = filmcraft_vp8::Decoder::new();
|
||||
let mut all = Vec::new();
|
||||
let mut disposable = Vec::new();
|
||||
for (i, f) in frames.iter().enumerate() {
|
||||
disposable.push(dec.is_disposable(f));
|
||||
if let Some(p) = dec.decode(f, i as i64).unwrap() {
|
||||
all.push((p.pts, common::i420(&p)));
|
||||
}
|
||||
}
|
||||
let stats = dec.stats();
|
||||
assert_eq!(stats.frames as usize, frames.len());
|
||||
assert_eq!(all.len() + stats.hidden_frames as usize, frames.len(), "{name}: one picture per shown frame");
|
||||
if name == "altref_2pass" {
|
||||
assert!(stats.hidden_frames > 0, "{name}: has hidden altref frames");
|
||||
}
|
||||
if name == "temporal_layers" {
|
||||
assert!(disposable.iter().filter(|&&d| d).count() >= frames.len() / 3, "{name}: the enhancement layer is disposable: {disposable:?}");
|
||||
}
|
||||
let mut dec = filmcraft_vp8::Decoder::new();
|
||||
for (i, f) in frames.iter().enumerate() {
|
||||
if disposable[i] {
|
||||
continue;
|
||||
}
|
||||
if let Some(p) = dec.decode(f, i as i64).unwrap() {
|
||||
let reference = all.iter().find(|(pts, _)| *pts == p.pts).expect("picture of a shown frame");
|
||||
assert!(common::i420(&p) == reference.1, "{name}: picture {} changed after leaving out disposable frames", p.pts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//! Damaged, truncated, reordered and random input must never panic (errors are fine).
|
||||
|
||||
mod common;
|
||||
|
||||
use filmcraft_vp8::{Decoder, Error};
|
||||
|
||||
fn xorshift(seed: &mut u64) -> u64 {
|
||||
*seed ^= *seed << 13;
|
||||
*seed ^= *seed >> 7;
|
||||
*seed ^= *seed << 17;
|
||||
*seed
|
||||
}
|
||||
|
||||
fn decode_all(frames: &[Vec<u8>]) {
|
||||
let mut dec = Decoder::new();
|
||||
for (i, f) in frames.iter().enumerate() {
|
||||
let _ = dec.is_disposable(f);
|
||||
let _ = dec.decode(f, i as i64);
|
||||
let _ = filmcraft_vp8::keyframe_info(f);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corrupted_streams_do_not_panic() {
|
||||
for name in ["default", "altref_2pass", "partitions8", "realtime_segments", "version3_fullpixel", "odd_size"] {
|
||||
let f = common::fixture(name);
|
||||
let Some((ivf, _)) = common::ensure(f) else { return };
|
||||
let data = std::fs::read(ivf).unwrap();
|
||||
let frames: Vec<Vec<u8>> = common::ivf_frames(&data).into_iter().map(|f| f.to_vec()).collect();
|
||||
let mut seed = 0x9e37_79b9_7f4a_7c15u64 ^ name.len() as u64;
|
||||
for round in 0..12 {
|
||||
let mut d = frames.clone();
|
||||
for fr in d.iter_mut() {
|
||||
if fr.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match round % 4 {
|
||||
0 => {
|
||||
for _ in 0..1 + round {
|
||||
let i = (xorshift(&mut seed) as usize) % fr.len();
|
||||
fr[i] ^= 1 << (xorshift(&mut seed) % 8);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
let n = (xorshift(&mut seed) as usize) % (fr.len() + 1);
|
||||
fr.truncate(n);
|
||||
}
|
||||
2 => {
|
||||
let i = (xorshift(&mut seed) as usize) % fr.len().min(24);
|
||||
fr[i] = xorshift(&mut seed) as u8;
|
||||
}
|
||||
// frames left intact; their order is shuffled below
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if round % 4 == 3 {
|
||||
for i in (1..d.len()).rev() {
|
||||
let j = (xorshift(&mut seed) as usize) % (i + 1);
|
||||
d.swap(i, j);
|
||||
}
|
||||
}
|
||||
decode_all(&d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_bytes_and_tiny_frames_do_not_panic() {
|
||||
let mut seed = 42u64;
|
||||
let mut dec = Decoder::new();
|
||||
for len in 0..200 {
|
||||
let f: Vec<u8> = (0..len).map(|_| xorshift(&mut seed) as u8).collect();
|
||||
let _ = dec.decode(&f, len as i64);
|
||||
}
|
||||
// a key frame header claiming a large size followed by garbage
|
||||
let mut key = vec![0u8; 64];
|
||||
key[0] = 0x10; // key frame, version 0, shown, first partition 0 bytes
|
||||
key[3..6].copy_from_slice(&[0x9d, 0x01, 0x2a]);
|
||||
key[6..8].copy_from_slice(&1920u16.to_le_bytes());
|
||||
key[8..10].copy_from_slice(&1080u16.to_le_bytes());
|
||||
let _ = dec.decode(&key, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inter_frame_without_key_frame_is_an_error() {
|
||||
let f = common::fixture("default");
|
||||
let Some((ivf, _)) = common::ensure(f) else { return };
|
||||
let data = std::fs::read(ivf).unwrap();
|
||||
let frames = common::ivf_frames(&data);
|
||||
let mut dec = Decoder::new();
|
||||
assert!(matches!(dec.decode(frames[1], 1), Err(Error::MissingReference(_))));
|
||||
// and decoding works from the next key frame on; after a reset again only from a key frame
|
||||
assert!(dec.decode(frames[0], 0).unwrap().is_some());
|
||||
assert!(dec.decode(frames[1], 1).unwrap().is_some());
|
||||
dec.reset();
|
||||
assert!(matches!(dec.decode(frames[2], 2), Err(Error::MissingReference(_))));
|
||||
assert!(filmcraft_vp8::is_keyframe(frames[0]) && !filmcraft_vp8::is_keyframe(frames[1]));
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//! The libvpx VP8 test vectors (`vp80-0*.ivf` with their `.md5` files of per-frame I420 MD5s),
|
||||
//! when present in `target/fixtures/vp8/conformance` or `$VP8_TEST_VECTORS`. They are not
|
||||
//! downloaded by the tests; fetch them from
|
||||
//! `https://storage.googleapis.com/downloads.webmproject.org/test_data/libvpx/<name>`.
|
||||
|
||||
mod common;
|
||||
|
||||
/// MD5 (RFC 1321), for comparing with the vectors' checksums.
|
||||
fn md5(data: &[u8]) -> String {
|
||||
let s: [u32; 64] = [
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4,
|
||||
11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
|
||||
];
|
||||
let k: Vec<u32> = (0..64).map(|i| ((i as f64 + 1.0).sin().abs() * 4294967296.0) as u32).collect();
|
||||
let (mut a0, mut b0, mut c0, mut d0) = (0x67452301u32, 0xefcdab89u32, 0x98badcfeu32, 0x10325476u32);
|
||||
let mut msg = data.to_vec();
|
||||
let bits = (data.len() as u64).wrapping_mul(8);
|
||||
msg.push(0x80);
|
||||
while msg.len() % 64 != 56 {
|
||||
msg.push(0);
|
||||
}
|
||||
msg.extend_from_slice(&bits.to_le_bytes());
|
||||
for chunk in msg.chunks(64) {
|
||||
let m: Vec<u32> = chunk.chunks(4).map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]])).collect();
|
||||
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
|
||||
for i in 0..64 {
|
||||
let (f, g) = match i / 16 {
|
||||
0 => ((b & c) | (!b & d), i),
|
||||
1 => ((d & b) | (!d & c), (5 * i + 1) % 16),
|
||||
2 => (b ^ c ^ d, (3 * i + 5) % 16),
|
||||
_ => (c ^ (b | !d), (7 * i) % 16),
|
||||
};
|
||||
let f = f.wrapping_add(a).wrapping_add(k[i]).wrapping_add(m[g]);
|
||||
a = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b = b.wrapping_add(f.rotate_left(s[i]));
|
||||
}
|
||||
a0 = a0.wrapping_add(a);
|
||||
b0 = b0.wrapping_add(b);
|
||||
c0 = c0.wrapping_add(c);
|
||||
d0 = d0.wrapping_add(d);
|
||||
}
|
||||
[a0, b0, c0, d0].iter().flat_map(|v| v.to_le_bytes()).map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn md5_matches_rfc_1321() {
|
||||
assert_eq!(md5(b""), "d41d8cd98f00b204e9800998ecf8427e");
|
||||
assert_eq!(md5(b"The quick brown fox jumps over the lazy dog"), "9e107d9d372bb6826bd81d3542a419d6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn libvpx_test_vectors() {
|
||||
let dir = std::env::var_os("VP8_TEST_VECTORS")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/fixtures/vp8/conformance"));
|
||||
let Ok(entries) = std::fs::read_dir(&dir) else {
|
||||
eprintln!("SKIP: no test vectors in {}", dir.display());
|
||||
return;
|
||||
};
|
||||
let mut names: Vec<_> = entries.filter_map(|e| e.ok()).map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "ivf")).collect();
|
||||
names.sort();
|
||||
let mut checked = 0;
|
||||
for ivf in names {
|
||||
let Ok(sums) = std::fs::read_to_string(ivf.with_extension("ivf.md5")) else { continue };
|
||||
let expected: Vec<&str> = sums.lines().filter_map(|l| l.split_whitespace().next()).collect();
|
||||
let data = std::fs::read(&ivf).unwrap();
|
||||
let pictures = common::decode_all(&common::ivf_frames(&data));
|
||||
assert_eq!(pictures.len(), expected.len(), "{}: picture count", ivf.display());
|
||||
for (i, (p, want)) in pictures.iter().zip(&expected).enumerate() {
|
||||
assert_eq!(md5(&common::i420(p)), *want, "{} frame {i}", ivf.display());
|
||||
}
|
||||
checked += 1;
|
||||
}
|
||||
eprintln!("{checked} test vectors bit-exact");
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the VP8 constant tables from the text of RFC 6386 ("VP8 Data Format and Decoding Guide").
|
||||
|
||||
Usage:
|
||||
curl -L -o rfc6386.txt https://www.rfc-editor.org/rfc/rfc6386.txt
|
||||
python3 extract_tables.py rfc6386.txt > ../src/spec_tables.rs
|
||||
|
||||
Only sections 1-19 (the format description) are read: the text is cut at section 20 ("Attachment
|
||||
One: Reference Decoder Source Code"), so nothing comes from the reference decoder. Every table is
|
||||
emitted flat (row-major, as printed) with its element count checked against the declared dimensions.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
cut = text.find("\n20. Attachment One")
|
||||
if cut < 0:
|
||||
sys.exit("section 20 not found")
|
||||
text = text[:cut]
|
||||
# page breaks: footer / header lines and form feeds
|
||||
text = "\n".join(l for l in text.split("\n") if not l.startswith("Bankoski") and not l.startswith("RFC 6386 ") and "\f" not in l)
|
||||
|
||||
|
||||
def table(name, count, occurrence=-1):
|
||||
"""Integers of the table `name ... = { ... }` (by default the last definition in the text)."""
|
||||
found = []
|
||||
# a definition: `name [dim] [dim] ... = {` (uses of the table never have `= {` after the brackets)
|
||||
for m in re.finditer(r"\b%s\s*(?:\[[^\]\n]*\]\s*)+=\s*\{" % re.escape(name), text):
|
||||
brace = m.end() - 1
|
||||
depth = 0
|
||||
i = brace
|
||||
while True:
|
||||
ch = text[i]
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
i += 1
|
||||
body = re.sub(r"/\*.*?\*/", "", text[brace:i], flags=re.S)
|
||||
body = re.sub(r"//[^\n]*", "", body)
|
||||
found.append([int(v) for v in re.findall(r"-?\d+", body)])
|
||||
if not found:
|
||||
sys.exit("table %s not found" % name)
|
||||
vals = found[occurrence]
|
||||
if len(vals) != count:
|
||||
sys.exit("table %s: %d values, expected %d" % (name, len(vals), count))
|
||||
return vals
|
||||
|
||||
|
||||
TABLES = [
|
||||
# (spec name, rust name, rust type, count)
|
||||
("coeff_update_probs", "COEFF_UPDATE_PROBS", "u8", 4 * 8 * 3 * 11),
|
||||
("default_coeff_probs", "DEFAULT_COEFF_PROBS", "u8", 4 * 8 * 3 * 11),
|
||||
("kf_bmode_prob", "KF_BMODE_PROBS", "u8", 10 * 10 * 9),
|
||||
("dc_qlookup", "DC_QLOOKUP", "u16", 128),
|
||||
("ac_qlookup", "AC_QLOOKUP", "u16", 128),
|
||||
("vp8_mode_contexts", "MODE_CONTEXTS", "u8", 6 * 4),
|
||||
("sub_mv_ref_prob", "SUB_MV_REF_PROBS", "u8", 5 * 3),
|
||||
("vp8_mv_update_probs", "MV_UPDATE_PROBS", "u8", 2 * 19),
|
||||
("default_mv_context", "DEFAULT_MV_PROBS", "u8", 2 * 19),
|
||||
("bmode_prob", "BMODE_PROBS", "u8", 9),
|
||||
("coeff_bands", "COEFF_BANDS", "u8", 16),
|
||||
("kf_ymode_prob", "KF_YMODE_PROBS", "u8", 4),
|
||||
("kf_uv_mode_prob", "KF_UV_MODE_PROBS", "u8", 3),
|
||||
("ymode_prob", "YMODE_PROBS", "u8", 4),
|
||||
("uv_mode_prob", "UV_MODE_PROBS", "u8", 3),
|
||||
("mvpartition_probs", "MVPARTITION_PROBS", "u8", 3),
|
||||
("BilinearFilters", "BILINEAR_FILTERS", "i32", 8 * 6),
|
||||
("filters", "SIXTAP_FILTERS", "i32", 8 * 6),
|
||||
]
|
||||
|
||||
print("// Generated by tools/extract_tables.py from the text of RFC 6386 (sections 1-19). Do not edit.")
|
||||
print("// Every table is flat, row-major, in the order the specification prints it.")
|
||||
print()
|
||||
for spec, rust, ty, count in TABLES:
|
||||
vals = table(spec, count)
|
||||
print("/// `%s` (RFC 6386), %d entries." % (spec, count))
|
||||
print("pub const %s: [%s; %d] = [" % (rust, ty, count))
|
||||
for i in range(0, count, 16):
|
||||
print(" " + ", ".join(str(v) for v in vals[i:i + 16]) + ",")
|
||||
print("];")
|
||||
print()
|
||||
@@ -44,6 +44,7 @@ const LAYERS: &[(&str, u8)] = &[
|
||||
("h264", 0),
|
||||
("h264enc", 0),
|
||||
("hevc", 0),
|
||||
("vp8", 0),
|
||||
("vp9", 0),
|
||||
("prores", 0),
|
||||
("mjpeg", 0),
|
||||
|
||||
Reference in new issue
Block a user