diff --git a/AGENTS.md b/AGENTS.md index eefd553a..f2dab8d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,30 @@ cargo run --example decode -- tests/data/bframes.264 out.yuv \ && cmp ref.yuv out.yuv && echo "decode matches ffmpeg" ``` +The `decode` example picks the codec from the extension (`.264`, `.265`, +`.obu`, `.ivf`). For AV1 the reference is dav1d, and `-noautoscale` keeps a +stream that changes size from being scaled to its first size (it applies to +any codec; H.264 and H.265 streams in `tests/data` do not change size): + +```bash +cargo run --example decode -- tests/data/av1/aom_altref.obu out.yuv \ + && ffmpeg -hide_banner -loglevel error -y -c:v libdav1d -i tests/data/av1/aom_altref.obu \ + -noautoscale -pix_fmt nv12 ref.yuv \ + && cmp ref.yuv out.yuv && echo "decode matches dav1d" +``` + +`tests/decode_streams.rs` does this for every fixture in `tests/data` (use +`p010le` for the 10-bit ones), and `tests/roundtrip.rs` for each codec's own +encoder output. A run with `PIXELFORGE_NO_UNIFIED_LAYOUTS=1` set takes the +copying path below. + +On NVIDIA (615.78.08) the AV1 decoder does not copy the `StdVideoAV1SequenceHeader` +handed to `vkCreateVideoSessionParametersKHR`: it reads it again at every +decode. The decoder keeps it alive for as long as the session parameters. Freed, +nothing fails and only the chroma of inter frames comes out slightly wrong, so +if AV1 inter frames ever differ from dav1d in chroma alone, look for a Std +structure that does not live long enough. + Decoding has two paths and both need checking. Where the device supports `VK_KHR_unified_image_layouts`, frames are the decoder's own images, handed over with no copy. Where it does not, they are copied into private images. Force the @@ -57,7 +81,8 @@ PIXELFORGE_NO_UNIFIED_LAYOUTS=1 cargo run --example decode -- \ ``` Which path a run took is in the debug log (`RUST_LOG=debug`), on the -`H.264 decode session:` line as `pinnable=`. +` decode session:` line (for example `H.264 decode session:`) as +`pinnable=`. The render path has its own example. `sample_frame` reads decoded frames in a compute shader through a sampler-YCbCr conversion, which is the path that never @@ -199,7 +224,8 @@ view by image handle, and only RADV without validation showed it. - `benches/` - The encode timing/throughput benchmark (`cargo bench`) - `testdata/` - Test input files (note: `test_frames.yuv` is a git-LFS pointer; generate frames locally if LFS content is unavailable) -- `tests/data/` - Small H.264 streams used by the decoder tests +- `tests/data/` - Small H.264, H.265 and AV1 (`tests/data/av1/`) streams used + by the decoder tests ## Key Dependencies - Vulkan Video API diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a10d420..31d2b6dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add H.265 decoding: `DecodeConfig::new(Codec::H265)`. Main and Main 10, open + and closed GOPs, joining a stream at a CRA, scaling lists, temporal layers. +- Add AV1 decoding: `DecodeConfig::new(Codec::AV1)`, for low-overhead OBU + streams. Hidden frames and `show_existing_frame`, tiles, superres, frame + size changes, 10-bit and film grain. - Add `EncodeConfig::new(codec, width, height)`, for a codec chosen at runtime. - Add RGB input: `EncodeConfig::with_source(Source::Rgb(..))`. The encoder @@ -52,6 +57,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Breaking:** `ColorConverter`, `ColorConverterConfig` and `OutputFormat` are no longer public. Describe an RGB source with `Source::Rgb` instead, and the encoder runs the converter itself, with the right target and range. +- **Breaking:** `DecodeConfig::new(codec)` replaces `DecodeConfig::h264()`, + in the same way as `EncodeConfig::new`. - **Breaking:** `RateControl` replaces `RateControlMode`, `quality_level`, `target_bitrate` and `max_bitrate`: `Cqp { qp }`, `Cbr { bitrate }` or `Vbr { target, max }`, so each mode carries exactly the values it uses. diff --git a/README.md b/README.md index 564deb2c..ad33025c 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ [![Documentation](https://docs.rs/pixelforge/badge.svg)](https://docs.rs/pixelforge) A Vulkan-based video encoding and decoding library for Rust, supporting H.264, -H.265 and AV1 encode, and H.264 decode. +H.265 and AV1, for both encode and decode. ## Features - **Hardware-accelerated** video encoding and decoding using Vulkan Video extensions. -- **Multiple codec support**: H.264/AVC, H.265/HEVC, AV1 encode; H.264 decode. +- **Multiple codec support**: H.264/AVC, H.265/HEVC and AV1, encode and decode. - **Asynchronous pipelines**: both directions submit without waiting. Encoding hands back an [`EncodeFuture`]; decoding delivers frames through a [`DecodeSource`] as the GPU finishes with them. @@ -37,11 +37,12 @@ H.265 and AV1 encode, and H.264 decode. | Codec | Encode | Decode | |-------|--------|--------| | H.264/AVC | ✓ | ✓ | -| H.265/HEVC | ✓ | | -| AV1 | ✓ | | +| H.265/HEVC | ✓ | ✓ | +| AV1 | ✓ | ✓ | H.264 decoding is verified byte-identical to `ffmpeg -pix_fmt nv12` on AMD -(RADV), NVIDIA and Intel (ANV). +(RADV), NVIDIA and Intel (ANV). H.265 decoding is verified byte-identical to +ffmpeg, and AV1 decoding (film grain included) to dav1d, on NVIDIA so far. ## Requirements @@ -152,7 +153,7 @@ fn main() -> Result<(), Box> { // A file can cut anywhere, so let the decoder frame it. Input that // arrives already framed (RTP, a container) skips `with_byte_stream`. - let config = DecodeConfig::h264().with_byte_stream(); + let config = DecodeConfig::new(Codec::H264).with_byte_stream(); let mut decoder = Decoder::new(context, config)?; let stream: Vec = std::fs::read("input.264")?; diff --git a/examples/common/mod.rs b/examples/common/mod.rs index 7a6fbe46..4be7a670 100644 --- a/examples/common/mod.rs +++ b/examples/common/mod.rs @@ -17,8 +17,8 @@ //! from the same thread. use ash::vk; -use pixelforge::decoder::{DecodedFrame, Decoder, FramePoll}; -use pixelforge::encoder::BitDepth; +use pixelforge::decoder::{DecodeConfig, DecodedFrame, Decoder, FramePoll}; +use pixelforge::encoder::{BitDepth, Codec}; use pixelforge::vulkan::VideoContext; use std::fs::File; use std::io::Write; @@ -26,20 +26,81 @@ use std::io::Write; /// Bytes handed to the decoder per call, standing in for a network read. pub const CHUNK_SIZE: usize = 64 * 1024; -/// Drive `decoder` over `stream`, calling `on_frame` for every frame it emits. +/// A coded stream read from a file. +pub enum Input { + /// A raw stream that the decoder frames itself: Annex B for H.264 and + /// H.265, low-overhead OBUs for AV1. + ByteStream(Vec), + /// Whole coded frames, as a container gives them (IVF). + Frames(Vec>), +} + +/// Read `path`, and pick the codec from its extension: `.264`/`.h264`, +/// `.265`/`.h265`/`.hevc`, `.obu`/`.av1` or `.ivf` (AV1 in IVF). +pub fn open_input(path: &str) -> Result<(Codec, Input), Box> { + let extension = std::path::Path::new(path) + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_ascii_lowercase(); + let data = std::fs::read(path)?; + Ok(match extension.as_str() { + "264" | "h264" => (Codec::H264, Input::ByteStream(data)), + "265" | "h265" | "hevc" => (Codec::H265, Input::ByteStream(data)), + "obu" | "av1" => (Codec::AV1, Input::ByteStream(data)), + "ivf" => (Codec::AV1, Input::Frames(ivf_frames(&data)?)), + other => return Err(format!("unknown stream extension {other:?}").into()), + }) +} + +/// The frames of an IVF file: a 32-byte file header, then each frame behind a +/// 12-byte header holding its size and timestamp. +fn ivf_frames(data: &[u8]) -> Result>, Box> { + if data.len() < 32 || &data[..4] != b"DKIF" { + return Err("not an IVF file".into()); + } + let header_len = u16::from_le_bytes([data[6], data[7]]) as usize; + let mut frames = Vec::new(); + let mut pos = header_len; + while pos + 12 <= data.len() { + let size = u32::from_le_bytes(data[pos..pos + 4].try_into()?) as usize; + let start = pos + 12; + let frame = data.get(start..start + size).ok_or("truncated IVF frame")?; + frames.push(frame.to_vec()); + pos = start + size; + } + Ok(frames) +} + +/// The decode configuration `input` needs: byte-stream framing for a raw +/// stream, frame-aligned for frames from a container. +pub fn decode_config(codec: Codec, input: &Input) -> DecodeConfig { + match input { + Input::ByteStream(_) => DecodeConfig::new(codec).with_byte_stream(), + Input::Frames(_) => DecodeConfig::new(codec), + } +} + +/// Drive `decoder` over `input`, calling `on_frame` for every frame it emits. /// -/// Frames are polled after each chunk and again after `finish`, so the -/// in-stream and end-of-stream paths share one callback. `NeedsKeyframe` is not +/// A raw stream is fed in chunks of [`CHUNK_SIZE`], standing in for network +/// reads; frames from a container are fed one per call. Frames are polled +/// after each call and again after `finish`, so the in-stream and +/// end-of-stream paths share one callback. `NeedsKeyframe` is not /// special-cased: polling then is a no-op when nothing is ready. -pub fn decode_stream( +pub fn decode_input( decoder: &mut Decoder, - stream: &[u8], + input: &Input, mut on_frame: F, ) -> Result<(), Box> where F: FnMut(DecodedFrame) -> Result<(), Box>, { - for (i, chunk) in stream.chunks(CHUNK_SIZE).enumerate() { + let chunks: Box> = match input { + Input::ByteStream(stream) => Box::new(stream.chunks(CHUNK_SIZE)), + Input::Frames(frames) => Box::new(frames.iter().map(Vec::as_slice)), + }; + for (i, chunk) in chunks.enumerate() { decoder.decode(chunk, i as u64)?; while let FramePoll::Frame(frame) = decoder.try_next_frame()? { on_frame(frame)?; @@ -283,10 +344,13 @@ impl Readback { // Crop to the visible region on the way out: the image is coded-size, // which is padded up to the codec's macroblock granularity. + // An odd size rounds the chroma up: the last chroma sample covers + // the last luma column (and row) on its own. let visible_y_stride = frame.width as usize * bytes; - let visible_uv_stride = visible_y_stride; + let visible_uv_stride = frame.width.div_ceil(2) as usize * 2 * bytes; + let uv_rows = frame.height.div_ceil(2) as usize; let mut y = Vec::with_capacity(visible_y_stride * frame.height as usize); - let mut uv = Vec::with_capacity(visible_uv_stride * frame.height as usize / 2); + let mut uv = Vec::with_capacity(visible_uv_stride * uv_rows); unsafe { // Map the whole allocation: a sub-range map has to respect // nonCoherentAtomSize, and WHOLE_SIZE sidesteps that. @@ -300,7 +364,7 @@ impl Readback { visible_y_stride, )); } - for row in 0..frame.height as usize / 2 { + for row in 0..uv_rows { uv.extend_from_slice(std::slice::from_raw_parts( ptr.add(y_size + row * uv_stride), visible_uv_stride, diff --git a/examples/decode.rs b/examples/decode.rs index 418a014a..58aac2e0 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -1,4 +1,7 @@ -//! Decode an H.264 Annex B stream to raw YUV. +//! Decode an H.264, H.265 or AV1 stream to raw YUV. +//! +//! The codec comes from the file extension: `.264`, `.265`/`.hevc`, `.obu` +//! (AV1 OBUs) or `.ivf` (AV1 in IVF). //! //! Usage: //! cargo run --example decode -- input.264 [output.yuv] @@ -13,10 +16,9 @@ use std::fs::File; #[allow(dead_code)] mod common; -use common::{Readback, decode_stream, write_nv12}; +use common::{Readback, decode_input, write_nv12}; -use pixelforge::decoder::{DecodeConfig, Decoder}; -use pixelforge::encoder::Codec; +use pixelforge::decoder::Decoder; use pixelforge::vulkan::VideoContextBuilder; fn main() -> Result<(), Box> { @@ -24,10 +26,11 @@ fn main() -> Result<(), Box> { let mut args = std::env::args().skip(1); let input_path = args.next().unwrap_or_else(|| { - eprintln!("usage: decode [output.yuv]"); + eprintln!("usage: decode [output.yuv]"); std::process::exit(1); }); let output_path = args.next(); + let (codec, input) = common::open_input(&input_path)?; // PIXELFORGE_VALIDATION=1 enables the Vulkan validation layers, which // report the exact offending call for any API misuse. @@ -36,7 +39,7 @@ fn main() -> Result<(), Box> { // hardware that would otherwise take the zero-copy path. let mut builder = VideoContextBuilder::new() .app_name("pixelforge-decode") - .require_decode(Codec::H264) + .require_decode(codec) .enable_validation(validation); if std::env::var("PIXELFORGE_NO_UNIFIED_LAYOUTS").is_ok() { builder = builder.without_unified_image_layouts(); @@ -59,7 +62,7 @@ fn main() -> Result<(), Box> { // sized for one frame per call, so raise it: past the budget the decoder // copies pictures out instead of handing over its own images, which is // still correct but gives up the zero-copy path. - let config = DecodeConfig::h264().with_byte_stream().with_output_depth(8); + let config = common::decode_config(codec, &input).with_output_depth(8); let mut output = output_path.as_ref().map(File::create).transpose()?; // Readback lives in the consumer, not in pixelforge: the decoder hands over // a GPU image and a renderer would sample it in place. See examples/common. @@ -69,7 +72,6 @@ fn main() -> Result<(), Box> { .then(|| Readback::new(&context)) .transpose()?; let mut decoder = Decoder::new(context, config)?; - let stream = std::fs::read(&input_path)?; let start = std::time::Instant::now(); let mut frame_count = 0usize; @@ -81,8 +83,8 @@ fn main() -> Result<(), Box> { // flight, not that there are none, so they are collected on a later pass. // A renderer would instead `split()` the decoder and await `next_frame` on // its own thread, which is what the `Decoder::split` docs show. - // `common::decode_stream` owns that loop. - decode_stream(&mut decoder, &stream, |frame| { + // `common::decode_input` owns that loop. + decode_input(&mut decoder, &input, |frame| { write_nv12(&frame, &mut readback, &mut output)?; // A rebuilt session means a new set of images, so anything caching // per-image state has to notice. Reporting it here is what makes the diff --git a/examples/sample_frame.rs b/examples/sample_frame.rs index 6ca9506c..894eaeb9 100644 --- a/examples/sample_frame.rs +++ b/examples/sample_frame.rs @@ -26,13 +26,12 @@ use std::fs::File; use std::io::Write; use ash::vk::{self, TaggedStructure as _}; -use pixelforge::decoder::{DecodeConfig, DecodedFrame, Decoder}; -use pixelforge::encoder::Codec; +use pixelforge::decoder::{DecodedFrame, Decoder}; use pixelforge::vulkan::{VideoContext, VideoContextBuilder}; #[allow(dead_code)] mod common; -use common::decode_stream; +use common::decode_input; const SHADER: &[u8] = include_bytes!("shader/sample_frame.spv"); @@ -41,15 +40,16 @@ fn main() -> Result<(), Box> { let mut args = std::env::args().skip(1); let input_path = args.next().unwrap_or_else(|| { - eprintln!("usage: sample_frame [output.rgba]"); + eprintln!("usage: sample_frame [output.rgba]"); std::process::exit(1); }); let output_path = args.next(); + let (codec, input) = common::open_input(&input_path)?; let validation = std::env::var("PIXELFORGE_VALIDATION").is_ok(); let context = VideoContextBuilder::new() .app_name("pixelforge-sample") - .require_decode(Codec::H264) + .require_decode(codec) .enable_validation(validation) .build()?; @@ -63,12 +63,10 @@ fn main() -> Result<(), Box> { // picture's sharing set. Without this, sampling a decoded image from a // family the decoder did not share with is undefined. let consumer_family = context.compute_queue_family(); - let config = DecodeConfig::h264() - .with_byte_stream() + let config = common::decode_config(codec, &input) .with_output_depth(8) .with_consumer_queue_family(consumer_family); - let stream = std::fs::read(&input_path)?; let mut output = output_path.as_ref().map(File::create).transpose()?; // Built lazily: the picture format is only known once the stream's first // parameter set has been parsed, and the ycbcr conversion depends on it. @@ -78,7 +76,7 @@ fn main() -> Result<(), Box> { let mut count = 0usize; let start = std::time::Instant::now(); - decode_stream(&mut decoder, &stream, |frame| { + decode_input(&mut decoder, &input, |frame| { let sampler = match &mut sampler { Some(s) => s, none => none.insert(Sampler::new(&context, consumer_family, &frame)?), diff --git a/examples/sample_planes.rs b/examples/sample_planes.rs index 7c48db1a..edb34db1 100644 --- a/examples/sample_planes.rs +++ b/examples/sample_planes.rs @@ -27,11 +27,10 @@ #[allow(dead_code)] mod common; -use common::decode_stream; +use common::decode_input; use ash::vk::{self, TaggedStructure as _}; -use pixelforge::decoder::{DecodeConfig, DecodedFrame, Decoder}; -use pixelforge::encoder::Codec; +use pixelforge::decoder::{DecodedFrame, Decoder}; use pixelforge::vulkan::{VideoContext, VideoContextBuilder}; use std::fs::File; use std::io::Write; @@ -43,15 +42,16 @@ fn main() -> Result<(), Box> { let mut args = std::env::args().skip(1); let input_path = args.next().unwrap_or_else(|| { - eprintln!("usage: sample_planes [output.yuv]"); + eprintln!("usage: sample_planes [output.yuv]"); std::process::exit(1); }); let output_path = args.next(); + let (codec, input) = common::open_input(&input_path)?; let validation = std::env::var("PIXELFORGE_VALIDATION").is_ok(); let context = VideoContextBuilder::new() .app_name("pixelforge-planes") - .require_decode(Codec::H264) + .require_decode(codec) .enable_validation(validation) .build()?; println!( @@ -61,19 +61,17 @@ fn main() -> Result<(), Box> { ); let consumer_family = context.compute_queue_family(); - let config = DecodeConfig::h264() - .with_byte_stream() + let config = common::decode_config(codec, &input) .with_output_depth(8) .with_consumer_queue_family(consumer_family); - let stream = std::fs::read(&input_path)?; let mut output = output_path.as_ref().map(File::create).transpose()?; let mut planes: Option = None; let mut decoder = Decoder::new(context.clone(), config)?; let mut count = 0usize; let start = std::time::Instant::now(); - decode_stream(&mut decoder, &stream, |frame| { + decode_input(&mut decoder, &input, |frame| { if !frame.plane_views { return Err( "this device does not allow per-plane views of decoded pictures; \ diff --git a/src/decoder/annexb.rs b/src/decoder/annexb.rs new file mode 100644 index 00000000..b403d827 --- /dev/null +++ b/src/decoder/annexb.rs @@ -0,0 +1,98 @@ +//! Annex B byte streams, as H.264 and H.265 both use them: NAL units separated +//! by 3- or 4-byte start codes. +//! +//! Only the splitting lives here. The NAL header differs between the two +//! codecs (one byte in H.264, two in H.265), so each codec parses its own. + +/// Annex B start code prefixed to each slice handed to the driver. +/// +/// Three bytes, not four. The leading zero byte of a 4-byte start code is legal +/// Annex B, and RADV decodes either form, but Intel's ANV decodes almost nothing +/// when a slice offset points at that extra zero: it reports no error and +/// returns a picture with a handful of macroblocks in it. ffmpeg's Vulkan +/// decoder uses three bytes for the same reason. +pub(crate) const START_CODE: [u8; 3] = [0, 0, 1]; + +/// Iterate over the NAL units in an Annex B byte stream. +/// +/// Yields each NAL's start-code offset within `data` together with the NAL +/// unit itself (header and EBSP payload, without the start code). Trailing +/// zero bytes before the next start code are trimmed, and empty NAL units are +/// skipped. +pub(crate) fn iter_nal_units(data: &[u8]) -> impl Iterator { + NalIterator { data, pos: 0 } +} + +struct NalIterator<'a> { + data: &'a [u8], + pos: usize, +} + +/// Find the next start code at or after `from`. Returns (start_code_pos, payload_pos). +fn find_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> { + let mut i = from; + while i + 3 <= data.len() { + if data[i] == 0 && data[i + 1] == 0 { + if data[i + 2] == 1 { + return Some((i, i + 3)); + } + if i + 4 <= data.len() && data[i + 2] == 0 && data[i + 3] == 1 { + return Some((i, i + 4)); + } + } + i += 1; + } + None +} + +impl<'a> Iterator for NalIterator<'a> { + type Item = (usize, &'a [u8]); + + fn next(&mut self) -> Option<(usize, &'a [u8])> { + loop { + let (start_code_pos, payload_start) = find_start_code(self.data, self.pos)?; + let end = match find_start_code(self.data, payload_start) { + Some((next_start, _)) => next_start, + None => self.data.len(), + }; + self.pos = end; + + // Trim trailing zero padding before the next start code. + let mut trimmed_end = end; + while trimmed_end > payload_start && self.data[trimmed_end - 1] == 0 { + trimmed_end -= 1; + } + if trimmed_end > payload_start { + return Some((start_code_pos, &self.data[payload_start..trimmed_end])); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn splits_on_start_codes_and_trims_zero_padding() { + let stream = [ + 0, 0, 0, 1, 0x67, 0xAA, 0, 0, 1, 0x68, 0xBB, 0, 0, 0, 0, 1, 0x65, + ]; + let nals: Vec<_> = iter_nal_units(&stream).collect(); + assert_eq!( + nals, + vec![ + (0, &[0x67, 0xAA][..]), + (6, &[0x68, 0xBB][..]), + (12, &[0x65][..]), + ] + ); + } + + #[test] + fn skips_empty_nal_units() { + let stream = [0, 0, 1, 0, 0, 1, 0x41]; + let nals: Vec<_> = iter_nal_units(&stream).collect(); + assert_eq!(nals, vec![(3, &[0x41][..])]); + } +} diff --git a/src/decoder/av1/bits.rs b/src/decoder/av1/bits.rs new file mode 100644 index 00000000..65bc3c5d --- /dev/null +++ b/src/decoder/av1/bits.rs @@ -0,0 +1,252 @@ +//! Bit reader for AV1 syntax (AV1 specification section 4.10 and 8.1). +//! +//! AV1 has no emulation prevention, so unlike the H.26x reader in +//! `crate::decoder::bitreader` this reads the bytes exactly as given. All +//! reads are bounds-checked and fail with `InvalidInput` past the end, so a +//! truncated or corrupt OBU can never panic the parser. + +use crate::error::{PixelForgeError, Result}; + +/// MSB-first bit reader over one OBU payload (or any byte slice). +#[derive(Debug, Clone)] +pub(crate) struct BitReader<'a> { + data: &'a [u8], + /// Position in bits from the start of `data`. + pos: usize, +} + +fn truncated() -> PixelForgeError { + PixelForgeError::InvalidInput("AV1: bitstream ended inside a syntax element".to_string()) +} + +impl<'a> BitReader<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data, pos: 0 } + } + + /// Current position in bits (the spec's `get_position()`). + pub fn position(&self) -> usize { + self.pos + } + + /// Current position in whole bytes, rounded up. + pub fn byte_position(&self) -> usize { + self.pos.div_ceil(8) + } + + /// Bits left before the end of the data. + pub fn bits_left(&self) -> usize { + (self.data.len() * 8).saturating_sub(self.pos) + } + + /// One bit as a flag. + pub fn flag(&mut self) -> Result { + Ok(self.f(1)? != 0) + } + + /// `f(n)`: an unsigned n-bit number, MSB first, for n up to 32. + pub fn f(&mut self, n: u32) -> Result { + debug_assert!(n <= 32); + if n == 0 { + return Ok(0); + } + if self.bits_left() < n as usize { + return Err(truncated()); + } + let mut value: u64 = 0; + for _ in 0..n { + let byte = self.data[self.pos >> 3]; + let bit = (byte >> (7 - (self.pos & 7))) & 1; + value = (value << 1) | bit as u64; + self.pos += 1; + } + Ok(value as u32) + } + + /// `su(n)`: an n-bit two's complement signed number (4.10.6). + pub fn su(&mut self, n: u32) -> Result { + let value = self.f(n)? as i64; + let sign_mask = 1i64 << (n - 1); + let value = if value & sign_mask != 0 { + value - 2 * sign_mask + } else { + value + }; + Ok(value as i32) + } + + /// `ns(n)`: a non-symmetric unsigned value in `0..n` (4.10.7). + pub fn ns(&mut self, n: u32) -> Result { + if n <= 1 { + // A single possible value takes no bits. + return Ok(0); + } + let w = 32 - n.leading_zeros(); // FloorLog2(n) + 1 + let m = (1u32 << w) - n; + let v = self.f(w - 1)?; + if v < m { + return Ok(v); + } + let extra_bit = self.f(1)?; + Ok((v << 1) - m + extra_bit) + } + + /// `le(n)`: an n-byte little-endian unsigned number (4.10.4). + pub fn le(&mut self, n: u32) -> Result { + let mut t: u64 = 0; + for i in 0..n { + t += (self.f(8)? as u64) << (i * 8); + } + u32::try_from(t) + .map_err(|_| PixelForgeError::InvalidInput("AV1: le(n) value out of range".to_string())) + } + + /// `leb128()`: an unsigned little-endian base-128 value (4.10.5). + pub fn leb128(&mut self) -> Result { + let mut value: u64 = 0; + for i in 0..8 { + let byte = self.f(8)? as u64; + value |= (byte & 0x7f) << (i * 7); + if byte & 0x80 == 0 { + return Ok(value); + } + } + Err(PixelForgeError::InvalidInput( + "AV1: leb128 value longer than 8 bytes".to_string(), + )) + } + + /// `uvlc()`: a variable-length unsigned value (4.10.3). + pub fn uvlc(&mut self) -> Result { + let mut leading_zeros = 0u32; + while !self.flag()? { + leading_zeros += 1; + if leading_zeros >= 32 { + // The spec defines this as (1 << 32) - 1; reading on would + // only consume bits that carry no information. + return Ok(u32::MAX); + } + } + let value = self.f(leading_zeros)?; + Ok(value + ((1u64 << leading_zeros) - 1) as u32) + } + + /// `byte_alignment()`: skip to the next byte boundary. The skipped bits + /// must be zero (5.3.5). + pub fn byte_alignment(&mut self) -> Result<()> { + while self.pos & 7 != 0 { + if self.flag()? { + return Err(PixelForgeError::InvalidInput( + "AV1: non-zero bit in byte_alignment()".to_string(), + )); + } + } + Ok(()) + } + + /// `trailing_bits()` (5.3.4): check that the bits from here to the end of + /// a payload of `payload_bytes` bytes are a one followed by zeros. + pub fn trailing_bits(&mut self, payload_bytes: usize) -> Result<()> { + let end = payload_bytes * 8; + if self.pos >= end || end > self.data.len() * 8 { + return Err(PixelForgeError::InvalidInput( + "AV1: OBU payload has no room for its trailing bits".to_string(), + )); + } + if !self.flag()? { + return Err(PixelForgeError::InvalidInput( + "AV1: OBU payload overruns its trailing bits".to_string(), + )); + } + while self.pos < end { + if self.flag()? { + return Err(PixelForgeError::InvalidInput( + "AV1: non-zero trailing_zero_bit".to_string(), + )); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reads_fixed_width_fields() { + let mut r = BitReader::new(&[0b1010_1100, 0xff, 0x01]); + assert_eq!(r.f(1).unwrap(), 1); + assert_eq!(r.f(3).unwrap(), 0b010); + assert_eq!(r.f(4).unwrap(), 0b1100); + assert_eq!(r.position(), 8); + assert_eq!(r.f(16).unwrap(), 0xff01); + assert!(r.f(1).is_err(), "reading past the end must fail"); + } + + #[test] + fn reads_signed_fields() { + // su(7) of 0b1111111 is -1, of 0b0111111 is 63, of 0b1000000 is -64. + let mut r = BitReader::new(&[0b1111_1110, 0b1111_1110, 0b0000_0000]); + assert_eq!(r.su(7).unwrap(), -1); + assert_eq!(r.su(7).unwrap(), 63); + assert_eq!(r.su(7).unwrap(), -64); + } + + #[test] + fn reads_leb128() { + // 0xE5 0x8E 0x26 is the classic 624485 example. + let mut r = BitReader::new(&[0xe5, 0x8e, 0x26, 0x05]); + assert_eq!(r.leb128().unwrap(), 624_485); + assert_eq!(r.leb128().unwrap(), 5); + assert!(BitReader::new(&[0x80; 9]).leb128().is_err()); + assert!(BitReader::new(&[0x80]).leb128().is_err()); + } + + #[test] + fn reads_uvlc() { + // "1" is 0, "010" is 1, "011" is 2, "00100" is 3. + let mut r = BitReader::new(&[0b1010_0110, 0b0100_0000]); + assert_eq!(r.uvlc().unwrap(), 0); + assert_eq!(r.uvlc().unwrap(), 1); + assert_eq!(r.uvlc().unwrap(), 2); + assert_eq!(r.uvlc().unwrap(), 3); + } + + #[test] + fn reads_ns() { + // ns(5): w = 3, m = 3. Values 0..3 take 2 bits; 3 and 4 take 3 bits. + let mut r = BitReader::new(&[0b1011_0111, 0b0000_0000]); + assert_eq!(r.ns(5).unwrap(), 2); // 10 + assert_eq!(r.ns(5).unwrap(), 3); // 11 0 -> (3 << 1) - 3 + 0 + assert_eq!(r.ns(5).unwrap(), 4); // 11 1 -> (3 << 1) - 3 + 1 + assert_eq!(r.position(), 8); + assert_eq!(r.ns(1).unwrap(), 0); + assert_eq!(r.position(), 8, "ns(1) reads nothing"); + } + + #[test] + fn reads_le() { + let mut r = BitReader::new(&[0x34, 0x12, 0x00, 0x01]); + assert_eq!(r.le(2).unwrap(), 0x1234); + assert_eq!(r.le(2).unwrap(), 0x0100); + } + + #[test] + fn checks_alignment_and_trailing_bits() { + let mut r = BitReader::new(&[0b1000_0000, 0b1100_0000]); + r.f(1).unwrap(); + r.byte_alignment().unwrap(); + assert_eq!(r.position(), 8); + r.f(1).unwrap(); + r.trailing_bits(2).unwrap(); + + let mut r = BitReader::new(&[0b1010_0000]); + r.f(1).unwrap(); + assert!(r.byte_alignment().is_err(), "non-zero padding bit"); + + let mut r = BitReader::new(&[0b0100_0001]); + r.f(1).unwrap(); + assert!(r.trailing_bits(1).is_err(), "stray bit after the one"); + } +} diff --git a/src/decoder/av1/consts.rs b/src/decoder/av1/consts.rs new file mode 100644 index 00000000..29ea997f --- /dev/null +++ b/src/decoder/av1/consts.rs @@ -0,0 +1,65 @@ +//! Constants from section 3 of the AV1 specification, as the parser uses them. + +pub const NUM_REF_FRAMES: usize = 8; +pub const REFS_PER_FRAME: usize = 7; +pub const TOTAL_REFS_PER_FRAME: usize = 8; +pub const PRIMARY_REF_NONE: u8 = 7; +pub const MAX_SEGMENTS: usize = 8; +pub const SEG_LVL_MAX: usize = 8; +pub const SEG_LVL_ALT_Q: usize = 0; +pub const SEG_LVL_REF_FRAME: usize = 5; +pub const MAX_LOOP_FILTER: i32 = 63; + +pub const MAX_TILE_WIDTH: u32 = 4096; +pub const MAX_TILE_AREA: u32 = 4096 * 2304; +pub const MAX_TILE_ROWS: u32 = 64; +pub const MAX_TILE_COLS: u32 = 64; + +pub const SUPERRES_NUM: u32 = 8; +pub const SUPERRES_DENOM_MIN: u32 = 9; +pub const SUPERRES_DENOM_BITS: u32 = 3; + +pub const SELECT_SCREEN_CONTENT_TOOLS: u8 = 2; +pub const SELECT_INTEGER_MV: u8 = 2; + +/// `interpolation_filter` value meaning "chosen per block". +pub const SWITCHABLE: u8 = 4; + +pub const RESTORATION_TILESIZE_MAX: u16 = 256; + +pub const WARPEDMODEL_PREC_BITS: u32 = 16; +pub const GM_ABS_ALPHA_BITS: u32 = 12; +pub const GM_ALPHA_PREC_BITS: u32 = 15; +pub const GM_ABS_TRANS_ONLY_BITS: u32 = 9; +pub const GM_TRANS_ONLY_PREC_BITS: u32 = 3; +pub const GM_ABS_TRANS_BITS: u32 = 12; +pub const GM_TRANS_PREC_BITS: u32 = 6; + +/// Global motion types (`GmType`). +pub const IDENTITY: u8 = 0; +pub const TRANSLATION: u8 = 1; +pub const ROTZOOM: u8 = 2; +pub const AFFINE: u8 = 3; + +/// Reference frame names (section 6.10.24), which index `OrderHints`, +/// `RefFrameSignBias`, `gm_params` and the loop filter reference deltas. +pub const LAST_FRAME: usize = 1; +pub const LAST2_FRAME: usize = 2; +pub const LAST3_FRAME: usize = 3; +pub const GOLDEN_FRAME: usize = 4; +pub const BWDREF_FRAME: usize = 5; +pub const ALTREF2_FRAME: usize = 6; +pub const ALTREF_FRAME: usize = 7; + +/// `loop_filter_ref_deltas` after `setup_past_independence()`. +pub const DEFAULT_LOOP_FILTER_REF_DELTAS: [i8; TOTAL_REFS_PER_FRAME] = [1, 0, 0, 0, -1, 0, -1, -1]; + +/// Identity warp parameters: `1 << WARPEDMODEL_PREC_BITS` on the diagonal. +pub const DEFAULT_GM_PARAMS: [i32; 6] = [ + 0, + 0, + 1 << WARPEDMODEL_PREC_BITS, + 0, + 0, + 1 << WARPEDMODEL_PREC_BITS, +]; diff --git a/src/decoder/av1/frame_header.rs b/src/decoder/av1/frame_header.rs new file mode 100644 index 00000000..cb378dd8 --- /dev/null +++ b/src/decoder/av1/frame_header.rs @@ -0,0 +1,663 @@ +//! The uncompressed frame header (AV1 specification section 5.9). +//! +//! The parser keeps coded values where Vulkan's `StdVideoDecodeAV1PictureInfo` +//! takes coded values, and derived ones (`OrderHint`, `OrderHints`, +//! `SkipModeFrame`, `TxMode`, `expectedFrameId`, the frame size after +//! superres) where it takes those. Values a frame inherits from its primary +//! reference frame (loop filter deltas, segmentation features, global motion +//! references, film grain) are resolved here, so a header is self-contained. +//! +//! Header parsing never changes the reference state it is given. The few +//! changes the syntax itself makes to that state (a shown key frame resetting +//! it, error resilient order hints and frame ids invalidating slots) are +//! applied to a copy, which the caller keeps for the reference frame update +//! once the frame is decoded. + +use super::bits::BitReader; +use super::consts::*; +use super::obu::ObuHeader; +use super::params::*; +use super::refs::{RefState, mark_ref_frames, relative_dist, set_frame_refs}; +use super::sequence::SequenceHeader; +use super::tile::{TileInfo, parse_tile_info}; +use crate::error::{PixelForgeError, Result}; + +/// `frame_type` (section 6.8.2), numbered as Vulkan's `StdVideoAV1FrameType`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[repr(u8)] +pub enum FrameType { + #[default] + Key = 0, + Inter = 1, + IntraOnly = 2, + Switch = 3, +} + +impl FrameType { + fn from_raw(raw: u32) -> Self { + match raw { + 0 => FrameType::Key, + 1 => FrameType::Inter, + 2 => FrameType::IntraOnly, + _ => FrameType::Switch, + } + } + + /// `FrameIsIntra`. + pub fn is_intra(self) -> bool { + matches!(self, FrameType::Key | FrameType::IntraOnly) + } +} + +/// The frame size syntax (5.9.5 to 5.9.8) and what it derives. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct FrameSize { + /// `FrameWidth`: the coded width, after superres downscaling. + pub frame_width: u32, + pub frame_height: u32, + /// `UpscaledWidth`: the width after superres upscaling. + pub upscaled_width: u32, + pub render_width: u32, + pub render_height: u32, + pub mi_cols: u32, + pub mi_rows: u32, + pub use_superres: bool, + /// The coded `coded_denom` (0 without superres), as Vulkan takes it. + pub coded_denom: u8, + /// `SuperresDenom` (8 without superres). + pub superres_denom: u32, + pub render_and_frame_size_different: bool, + /// With `frame_size_with_refs()`, which of the seven references the size + /// was copied from (`found_ref`). + pub found_ref: Option, +} + +/// A parsed `uncompressed_header()`. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct FrameHeader { + pub show_existing_frame: bool, + pub frame_to_show_map_idx: u8, + pub display_frame_id: u32, + pub frame_presentation_time: u32, + pub frame_type: FrameType, + pub show_frame: bool, + pub showable_frame: bool, + pub error_resilient_mode: bool, + pub disable_cdf_update: bool, + pub allow_screen_content_tools: bool, + pub force_integer_mv: bool, + pub current_frame_id: u32, + pub frame_size_override_flag: bool, + /// `OrderHint`. + pub order_hint: u8, + pub primary_ref_frame: u8, + pub buffer_removal_time_present_flag: bool, + pub refresh_frame_flags: u8, + /// The coded `ref_order_hint[]` (error resilient frames only). + pub ref_order_hint: [u8; NUM_REF_FRAMES], + pub frame_size: FrameSize, + pub allow_intrabc: bool, + pub frame_refs_short_signaling: bool, + pub last_frame_idx: u8, + pub gold_frame_idx: u8, + /// The reference slot behind each of LAST_FRAME..=ALTREF_FRAME (coded, + /// or derived by `set_frame_refs()`). Zero for intra frames. + pub ref_frame_idx: [u8; REFS_PER_FRAME], + /// `expectedFrameId[i]` for reference `LAST_FRAME + i`, as section 5.9.2 + /// indexes it (Vulkan's array has an eighth, unused entry). + pub expected_frame_id: [u32; REFS_PER_FRAME], + pub allow_high_precision_mv: bool, + pub is_filter_switchable: bool, + /// `interpolation_filter`, `SWITCHABLE` (4) when switchable. + pub interpolation_filter: u8, + pub is_motion_mode_switchable: bool, + pub use_ref_frame_mvs: bool, + /// `OrderHints[refFrame]`, indexed by reference frame name (entry 0, + /// INTRA_FRAME, is unused). + pub order_hints: [u8; TOTAL_REFS_PER_FRAME], + /// `RefFrameSignBias[refFrame]`, indexed by reference frame name. + pub ref_frame_sign_bias: [bool; TOTAL_REFS_PER_FRAME], + pub disable_frame_end_update_cdf: bool, + pub tile_info: TileInfo, + pub quantization: QuantizationParams, + pub segmentation: SegmentationParams, + pub delta: DeltaParams, + pub coded_lossless: bool, + pub all_lossless: bool, + pub lossless_array: [bool; MAX_SEGMENTS], + /// `SegQMLevel[plane][segment]`, set when `using_qmatrix`. + pub seg_qm_level: [[u8; MAX_SEGMENTS]; 3], + pub loop_filter: LoopFilterParams, + pub cdef: CdefParams, + pub loop_restoration: LoopRestorationParams, + /// `TxMode`: 0 ONLY_4X4, 1 TX_MODE_LARGEST, 2 TX_MODE_SELECT. + pub tx_mode: u8, + pub reference_select: bool, + pub skip_mode_present: bool, + /// `SkipModeFrame[0..2]` as reference frame names; zero when skip mode is + /// not allowed. + pub skip_mode_frame: [u8; 2], + pub allow_warped_motion: bool, + pub reduced_tx_set: bool, + pub global_motion: GlobalMotionParams, + pub film_grain: FilmGrainParams, + /// Length of `uncompressed_header()` in bits, without the byte + /// alignment or trailing bits that follow it. + pub header_bits: usize, +} + +impl FrameHeader { + /// `FrameIsIntra`. + pub fn frame_is_intra(&self) -> bool { + self.frame_type.is_intra() + } + + /// `RefFrameSignBias` as a bitmask indexed by reference frame name. + pub fn ref_frame_sign_bias_mask(&self) -> u8 { + self.ref_frame_sign_bias + .iter() + .enumerate() + .fold(0, |mask, (i, &b)| mask | ((b as u8) << i)) + } +} + +/// What parsing a frame header produced. +#[derive(Debug)] +pub(crate) enum HeaderOutcome { + /// The header, and the reference state with the header's own changes + /// applied, for the update once the frame is decoded. + Parsed(Box, Box), + /// The header needs reference slots that hold no valid frame (bit `i` + /// for slot `i`), so the frame cannot be decoded; a decoder has to wait + /// for the next key frame. + MissingReferences(u8), +} + +fn frame_size( + r: &mut BitReader, + seq: &SequenceHeader, + frame_size_override_flag: bool, +) -> Result { + let mut size = FrameSize::default(); + if frame_size_override_flag { + size.frame_width = r.f(seq.frame_width_bits_minus_1 as u32 + 1)? + 1; + size.frame_height = r.f(seq.frame_height_bits_minus_1 as u32 + 1)? + 1; + } else { + size.frame_width = seq.max_frame_width(); + size.frame_height = seq.max_frame_height(); + } + superres_params(r, seq, &mut size)?; + Ok(size) +} + +/// `superres_params()` followed by `compute_image_size()`. +fn superres_params(r: &mut BitReader, seq: &SequenceHeader, size: &mut FrameSize) -> Result<()> { + size.use_superres = seq.enable_superres && r.flag()?; + size.superres_denom = if size.use_superres { + size.coded_denom = r.f(SUPERRES_DENOM_BITS)? as u8; + size.coded_denom as u32 + SUPERRES_DENOM_MIN + } else { + SUPERRES_NUM + }; + size.upscaled_width = size.frame_width; + size.frame_width = + (size.upscaled_width * SUPERRES_NUM + size.superres_denom / 2) / size.superres_denom; + size.mi_cols = 2 * ((size.frame_width + 7) >> 3); + size.mi_rows = 2 * ((size.frame_height + 7) >> 3); + Ok(()) +} + +fn render_size(r: &mut BitReader, size: &mut FrameSize) -> Result<()> { + size.render_and_frame_size_different = r.flag()?; + if size.render_and_frame_size_different { + size.render_width = r.f(16)? + 1; + size.render_height = r.f(16)? + 1; + } else { + size.render_width = size.upscaled_width; + size.render_height = size.frame_height; + } + Ok(()) +} + +/// `frame_size_with_refs()` (5.9.7). +fn frame_size_with_refs( + r: &mut BitReader, + seq: &SequenceHeader, + refs: &RefState, + ref_frame_idx: &[u8; REFS_PER_FRAME], + frame_size_override_flag: bool, +) -> Result { + for (i, &idx) in ref_frame_idx.iter().enumerate() { + if r.flag()? { + let slot = &refs.slots[idx as usize]; + let mut size = FrameSize { + frame_width: slot.upscaled_width, + frame_height: slot.frame_height, + render_width: slot.render_width, + render_height: slot.render_height, + found_ref: Some(i as u8), + ..Default::default() + }; + superres_params(r, seq, &mut size)?; + return Ok(size); + } + } + let mut size = frame_size(r, seq, frame_size_override_flag)?; + render_size(r, &mut size)?; + Ok(size) +} + +/// Fill a `show_existing_frame` header from the slot it shows (the parts of +/// the loading process, 7.21, a decoder needs to output the frame). +fn load_shown_frame(seq: &SequenceHeader, refs: &RefState, h: &mut FrameHeader) { + let slot = &refs.slots[h.frame_to_show_map_idx as usize]; + h.frame_type = slot.frame_type; + h.order_hint = slot.order_hint; + h.order_hints = slot.saved_order_hints; + h.current_frame_id = slot.frame_id; + h.frame_size = FrameSize { + frame_width: slot.frame_width, + frame_height: slot.frame_height, + upscaled_width: slot.upscaled_width, + render_width: slot.render_width, + render_height: slot.render_height, + mi_cols: slot.mi_cols, + mi_rows: slot.mi_rows, + superres_denom: SUPERRES_NUM, + ..Default::default() + }; + if seq.film_grain_params_present { + h.film_grain = slot.film_grain.clone(); + } + if h.frame_type == FrameType::Key { + h.refresh_frame_flags = 0xff; + } +} + +/// Parse `uncompressed_header()` against the current reference state. +/// +/// `prev_order_hints` is `OrderHints` as the previous frame header left it: +/// the spec keeps it across frames, and an intra frame stores it unchanged. +pub(crate) fn parse_uncompressed_header( + r: &mut BitReader, + seq: &SequenceHeader, + refs: &RefState, + obu: &ObuHeader, + prev_order_hints: [u8; TOTAL_REFS_PER_FRAME], +) -> Result { + let start = r.position(); + let mut refs = refs.clone(); + let mut h = FrameHeader { + order_hints: prev_order_hints, + ..Default::default() + }; + let id_len = seq.frame_id_length(); + let decoder_model = seq.decoder_model_info.as_ref(); + let all_frames = 0xffu8; + + if seq.reduced_still_picture_header { + h.frame_type = FrameType::Key; + h.show_frame = true; + h.error_resilient_mode = true; + } else { + h.show_existing_frame = r.flag()?; + if h.show_existing_frame { + h.frame_to_show_map_idx = r.f(3)? as u8; + if let Some(model) = decoder_model + && !seq.equal_picture_interval() + { + h.frame_presentation_time = + r.f(model.frame_presentation_time_length_minus_1 as u32 + 1)?; + } + if seq.frame_id_numbers_present_flag { + h.display_frame_id = r.f(id_len)?; + } + if !refs.slots[h.frame_to_show_map_idx as usize].valid { + return Ok(HeaderOutcome::MissingReferences( + 1 << h.frame_to_show_map_idx, + )); + } + load_shown_frame(seq, &refs, &mut h); + h.header_bits = r.position() - start; + return Ok(HeaderOutcome::Parsed(Box::new(h), Box::new(refs))); + } + h.frame_type = FrameType::from_raw(r.f(2)?); + h.show_frame = r.flag()?; + if h.show_frame + && let Some(model) = decoder_model + && !seq.equal_picture_interval() + { + h.frame_presentation_time = + r.f(model.frame_presentation_time_length_minus_1 as u32 + 1)?; + } + h.showable_frame = if h.show_frame { + h.frame_type != FrameType::Key + } else { + r.flag()? + }; + h.error_resilient_mode = if h.frame_type == FrameType::Switch + || (h.frame_type == FrameType::Key && h.show_frame) + { + true + } else { + r.flag()? + }; + } + let frame_is_intra = h.frame_is_intra(); + + if h.frame_type == FrameType::Key && h.show_frame { + for slot in refs.slots.iter_mut() { + slot.valid = false; + slot.order_hint = 0; + } + for i in 0..REFS_PER_FRAME { + h.order_hints[LAST_FRAME + i] = 0; + } + } + h.disable_cdf_update = r.flag()?; + h.allow_screen_content_tools = + if seq.seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS { + r.flag()? + } else { + seq.seq_force_screen_content_tools != 0 + }; + if h.allow_screen_content_tools { + h.force_integer_mv = if seq.seq_force_integer_mv == SELECT_INTEGER_MV { + r.flag()? + } else { + seq.seq_force_integer_mv != 0 + }; + } + if frame_is_intra { + h.force_integer_mv = true; + } + if seq.frame_id_numbers_present_flag { + h.current_frame_id = r.f(id_len)?; + mark_ref_frames(seq, &mut refs, h.current_frame_id); + } + h.frame_size_override_flag = if h.frame_type == FrameType::Switch { + true + } else if seq.reduced_still_picture_header { + false + } else { + r.flag()? + }; + h.order_hint = r.f(seq.order_hint_bits as u32)? as u8; + h.primary_ref_frame = if frame_is_intra || h.error_resilient_mode { + PRIMARY_REF_NONE + } else { + r.f(3)? as u8 + }; + if let Some(model) = decoder_model { + h.buffer_removal_time_present_flag = r.flag()?; + if h.buffer_removal_time_present_flag { + // The removal times are only for the decoder model; skip them. + for op in &seq.operating_points { + if op.decoder_model_present_for_this_op { + let idc = op.operating_point_idc; + let in_temporal = (idc >> obu.temporal_id) & 1 != 0; + let in_spatial = (idc >> (obu.spatial_id + 8)) & 1 != 0; + if idc == 0 || (in_temporal && in_spatial) { + r.f(model.buffer_removal_time_length_minus_1 as u32 + 1)?; + } + } + } + } + } + h.refresh_frame_flags = + if h.frame_type == FrameType::Switch || (h.frame_type == FrameType::Key && h.show_frame) { + all_frames + } else { + r.f(8)? as u8 + }; + if h.frame_type == FrameType::IntraOnly && h.refresh_frame_flags == all_frames { + return Err(PixelForgeError::InvalidInput( + "AV1: intra-only frame refreshes every reference slot".to_string(), + )); + } + if (!frame_is_intra || h.refresh_frame_flags != all_frames) + && h.error_resilient_mode + && seq.enable_order_hint + { + for i in 0..NUM_REF_FRAMES { + let hint = r.f(seq.order_hint_bits as u32)? as u8; + h.ref_order_hint[i] = hint; + let slot = &mut refs.slots[i]; + if hint != slot.order_hint { + // The frame the encoder had here is lost. Remember the order + // hint it had, as libaom does, so later order arithmetic + // matches the encoder's; the slot is unusable either way. + slot.valid = false; + slot.order_hint = hint; + } + } + } + + if frame_is_intra { + h.frame_size = frame_size(r, seq, h.frame_size_override_flag)?; + render_size(r, &mut h.frame_size)?; + if h.allow_screen_content_tools && h.frame_size.upscaled_width == h.frame_size.frame_width { + h.allow_intrabc = r.flag()?; + } + } else { + if seq.enable_order_hint { + h.frame_refs_short_signaling = r.flag()?; + if h.frame_refs_short_signaling { + h.last_frame_idx = r.f(3)? as u8; + h.gold_frame_idx = r.f(3)? as u8; + h.ref_frame_idx = + set_frame_refs(seq, &refs, h.order_hint, h.last_frame_idx, h.gold_frame_idx); + } + } + let mut missing = 0u8; + for i in 0..REFS_PER_FRAME { + if !h.frame_refs_short_signaling { + h.ref_frame_idx[i] = r.f(3)? as u8; + } + let slot = &refs.slots[h.ref_frame_idx[i] as usize]; + if seq.frame_id_numbers_present_flag { + let delta = r.f(seq.delta_frame_id_length_minus_2 as u32 + 2)? + 1; + let expected = (h.current_frame_id + (1 << id_len) - delta) % (1 << id_len); + h.expected_frame_id[i] = expected; + if expected != slot.frame_id { + missing |= 1 << h.ref_frame_idx[i]; + } + } + if !slot.valid { + missing |= 1 << h.ref_frame_idx[i]; + } + } + if missing != 0 { + return Ok(HeaderOutcome::MissingReferences(missing)); + } + h.frame_size = if h.frame_size_override_flag && !h.error_resilient_mode { + frame_size_with_refs(r, seq, &refs, &h.ref_frame_idx, h.frame_size_override_flag)? + } else { + let mut size = frame_size(r, seq, h.frame_size_override_flag)?; + render_size(r, &mut size)?; + size + }; + h.allow_high_precision_mv = if h.force_integer_mv { false } else { r.flag()? }; + h.is_filter_switchable = r.flag()?; + h.interpolation_filter = if h.is_filter_switchable { + SWITCHABLE + } else { + r.f(2)? as u8 + }; + h.is_motion_mode_switchable = r.flag()?; + h.use_ref_frame_mvs = if h.error_resilient_mode || !seq.enable_ref_frame_mvs { + false + } else { + r.flag()? + }; + for i in 0..REFS_PER_FRAME { + let ref_frame = LAST_FRAME + i; + let hint = refs.slots[h.ref_frame_idx[i] as usize].order_hint; + h.order_hints[ref_frame] = hint; + h.ref_frame_sign_bias[ref_frame] = + seq.enable_order_hint && relative_dist(seq, hint, h.order_hint) > 0; + } + } + + h.disable_frame_end_update_cdf = if seq.reduced_still_picture_header || h.disable_cdf_update { + true + } else { + r.flag()? + }; + + // load_previous() or setup_past_independence(): what the frame inherits. + let (prev_gm_params, prev_lf_ref, prev_lf_mode, prev_segmentation) = + if h.primary_ref_frame == PRIMARY_REF_NONE { + ( + [DEFAULT_GM_PARAMS; TOTAL_REFS_PER_FRAME], + DEFAULT_LOOP_FILTER_REF_DELTAS, + [0i8; 2], + SegmentationParams::default(), + ) + } else { + let slot = &refs.slots[h.ref_frame_idx[h.primary_ref_frame as usize] as usize]; + ( + slot.gm_params, + slot.loop_filter_ref_deltas, + slot.loop_filter_mode_deltas, + SegmentationParams { + feature_enabled: slot.feature_enabled, + feature_data: slot.feature_data, + ..Default::default() + }, + ) + }; + + h.tile_info = parse_tile_info( + r, + seq.use_128x128_superblock, + h.frame_size.mi_cols, + h.frame_size.mi_rows, + )?; + h.quantization = parse_quantization(r, seq)?; + h.segmentation = parse_segmentation(r, h.primary_ref_frame, &prev_segmentation)?; + h.delta = parse_delta_params(r, h.quantization.base_q_idx, h.allow_intrabc)?; + + let q = &h.quantization; + h.coded_lossless = true; + for segment_id in 0..MAX_SEGMENTS { + let qindex = h.segmentation.qindex(q.base_q_idx, segment_id); + let lossless = qindex == 0 + && q.delta_q_y_dc == 0 + && q.delta_q_u_ac == 0 + && q.delta_q_u_dc == 0 + && q.delta_q_v_ac == 0 + && q.delta_q_v_dc == 0; + h.lossless_array[segment_id] = lossless; + if !lossless { + h.coded_lossless = false; + } + if q.using_qmatrix { + let levels = if lossless { + [15, 15, 15] + } else { + [q.qm_y, q.qm_u, q.qm_v] + }; + for (plane, level) in levels.into_iter().enumerate() { + h.seg_qm_level[plane][segment_id] = level; + } + } + } + h.all_lossless = h.coded_lossless && h.frame_size.frame_width == h.frame_size.upscaled_width; + + h.loop_filter = parse_loop_filter( + r, + seq, + h.coded_lossless, + h.allow_intrabc, + prev_lf_ref, + prev_lf_mode, + )?; + h.cdef = parse_cdef(r, seq, h.coded_lossless, h.allow_intrabc)?; + h.loop_restoration = parse_lr(r, seq, h.all_lossless, h.allow_intrabc)?; + h.tx_mode = if h.coded_lossless { + 0 + } else if r.flag()? { + 2 + } else { + 1 + }; + h.reference_select = !frame_is_intra && r.flag()?; + h.skip_mode_frame = skip_mode_frames(seq, &refs, &h); + h.skip_mode_present = h.skip_mode_frame[0] != 0 && r.flag()?; + h.allow_warped_motion = + !(frame_is_intra || h.error_resilient_mode || !seq.enable_warped_motion) && r.flag()?; + h.reduced_tx_set = r.flag()?; + h.global_motion = parse_global_motion( + r, + frame_is_intra, + h.allow_high_precision_mv, + &prev_gm_params, + )?; + + let ctx = GrainContext { + show_frame: h.show_frame, + showable_frame: h.showable_frame, + is_inter_frame: h.frame_type == FrameType::Inter, + }; + let load = |idx: u8| { + let slot = &refs.slots[idx as usize]; + slot.valid.then(|| slot.film_grain.clone()) + }; + h.film_grain = match parse_film_grain(r, seq, &ctx, load)? { + Ok(grain) => grain, + Err(slot) => return Ok(HeaderOutcome::MissingReferences(1 << slot)), + }; + + h.header_bits = r.position() - start; + Ok(HeaderOutcome::Parsed(Box::new(h), Box::new(refs))) +} + +/// `skip_mode_params()` (5.9.22) up to `skipModeAllowed`: the two +/// reference frame names skip mode would use, or zeros when it is not +/// allowed. +fn skip_mode_frames(seq: &SequenceHeader, refs: &RefState, h: &FrameHeader) -> [u8; 2] { + if h.frame_is_intra() || !h.reference_select || !seq.enable_order_hint { + return [0, 0]; + } + let hint_of = |i: usize| refs.slots[h.ref_frame_idx[i] as usize].order_hint; + let dist = |a: u8, b: u8| relative_dist(seq, a, b); + let mut forward: Option<(usize, u8)> = None; + let mut backward: Option<(usize, u8)> = None; + for i in 0..REFS_PER_FRAME { + let ref_hint = hint_of(i); + if dist(ref_hint, h.order_hint) < 0 { + if forward.is_none_or(|(_, f)| dist(ref_hint, f) > 0) { + forward = Some((i, ref_hint)); + } + } else if dist(ref_hint, h.order_hint) > 0 + && backward.is_none_or(|(_, b)| dist(ref_hint, b) < 0) + { + backward = Some((i, ref_hint)); + } + } + let Some((forward_idx, forward_hint)) = forward else { + return [0, 0]; + }; + let second = if let Some((backward_idx, _)) = backward { + backward_idx + } else { + let mut second_forward: Option<(usize, u8)> = None; + for i in 0..REFS_PER_FRAME { + let ref_hint = hint_of(i); + if dist(ref_hint, forward_hint) < 0 + && second_forward.is_none_or(|(_, s)| dist(ref_hint, s) > 0) + { + second_forward = Some((i, ref_hint)); + } + } + let Some((second_idx, _)) = second_forward else { + return [0, 0]; + }; + second_idx + }; + [ + (LAST_FRAME + forward_idx.min(second)) as u8, + (LAST_FRAME + forward_idx.max(second)) as u8, + ] +} diff --git a/src/decoder/av1/mod.rs b/src/decoder/av1/mod.rs new file mode 100644 index 00000000..925d4f88 --- /dev/null +++ b/src/decoder/av1/mod.rs @@ -0,0 +1,42 @@ +//! Host-side AV1 parsing for Vulkan Video decode (`VK_KHR_video_decode_av1`). +//! +//! Vulkan decodes AV1 tiles in the driver but takes every header value from +//! the application, already parsed: the sequence header, the frame header +//! with everything it inherits from its references, and where each tile is. +//! This module produces exactly that from a low-overhead OBU stream +//! (section 5.2 of the AV1 specification), and keeps the reference state +//! (the eight reference slots) that later headers depend on. +//! +//! - [`obu`]: OBU framing, temporal unit boundaries, operating point +//! selection, Annex B detection. +//! - [`sequence`]: the sequence header. +//! - [`frame_header`] and [`params`]: the uncompressed frame header. +//! - [`tile`]: the tile grid and tile group OBUs. +//! - [`refs`]: the reference slots and their update. +//! - [`parser`]: [`Av1Parser`], which ties them together frame by frame. +//! - [`std_info`]: conversion to the Vulkan `StdVideoAV1*` structures. +//! +//! Typical use: iterate the OBUs of a temporal unit with +//! [`obu::iter_obus`], pass each to [`Av1Parser::parse_obu`], and for each +//! [`Av1Event::Frame`] decode it, then call [`Av1Parser::commit_frame`] +//! with the DPB slot the picture went to. + +pub(crate) mod bits; +pub(crate) mod consts; +pub(crate) mod frame_header; +pub(crate) mod obu; +pub(crate) mod params; +pub(crate) mod parser; +pub(crate) mod refs; +pub(crate) mod sequence; +pub(crate) mod std_info; +pub(crate) mod tile; + +mod record; +mod session; + +/// Parser tests against real libaom and SVT-AV1 streams (host-side, no GPU). +#[cfg(test)] +mod stream_tests; + +pub(crate) use session::Av1Decoder; diff --git a/src/decoder/av1/obu.rs b/src/decoder/av1/obu.rs new file mode 100644 index 00000000..1fa809bb --- /dev/null +++ b/src/decoder/av1/obu.rs @@ -0,0 +1,357 @@ +//! OBU framing (AV1 specification sections 5.2 and 5.3). +//! +//! The parser takes the low-overhead bitstream format of section 5.2, the +//! format of `.obu` files, of MP4/Matroska samples and of RTP after +//! depacketisation: a plain sequence of OBUs, each carrying its own size. +//! Annex B length-delimited streams wrap every temporal unit, frame unit and +//! OBU in a further length; they are detected where the data makes it +//! possible and rejected with an error that says so. + +use super::bits::BitReader; +use crate::error::{PixelForgeError, Result}; + +/// OBU types (section 6.2.2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ObuType { + SequenceHeader, + TemporalDelimiter, + FrameHeader, + TileGroup, + Metadata, + Frame, + RedundantFrameHeader, + TileList, + Padding, + /// A reserved type, which decoders must ignore. + Reserved(u8), +} + +impl ObuType { + fn from_raw(raw: u8) -> Self { + match raw { + 1 => ObuType::SequenceHeader, + 2 => ObuType::TemporalDelimiter, + 3 => ObuType::FrameHeader, + 4 => ObuType::TileGroup, + 5 => ObuType::Metadata, + 6 => ObuType::Frame, + 7 => ObuType::RedundantFrameHeader, + 8 => ObuType::TileList, + 15 => ObuType::Padding, + other => ObuType::Reserved(other), + } + } +} + +/// `obu_header()` (section 5.3.2), plus whether the size field was present. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ObuHeader { + pub obu_type: ObuType, + pub obu_extension_flag: bool, + pub obu_has_size_field: bool, + /// Zero when there is no extension header. + pub temporal_id: u8, + /// Zero when there is no extension header. + pub spatial_id: u8, +} + +/// One OBU within a buffer. +#[derive(Debug, Clone, Copy)] +pub struct Obu<'a> { + pub header: ObuHeader, + /// Byte offset of the first byte of `obu_header()` within the data the + /// iterator was given. + pub offset: usize, + /// Bytes from `offset` to the payload: the header, the optional + /// extension byte and the optional `obu_size` field. + pub header_len: usize, + /// The payload (`obu_size` bytes), without the header. + pub payload: &'a [u8], +} + +impl Obu<'_> { + /// Byte offset of the payload within the data the iterator was given. + pub fn payload_offset(&self) -> usize { + self.offset + self.header_len + } +} + +/// Whether an OBU belongs to the operating point with the given +/// `operating_point_idc` (section 6.2.1, the `drop_obu()` rule). +/// +/// OBUs without an extension header, sequence headers and temporal +/// delimiters always belong, as does everything when `idc` is zero. +pub fn in_operating_point(header: &ObuHeader, idc: u16) -> bool { + if idc == 0 + || !header.obu_extension_flag + || matches!( + header.obu_type, + ObuType::SequenceHeader | ObuType::TemporalDelimiter + ) + { + return true; + } + let in_temporal = (idc >> header.temporal_id) & 1 != 0; + let in_spatial = (idc >> (header.spatial_id + 8)) & 1 != 0; + in_temporal && in_spatial +} + +/// Read one OBU from the start of `data`. +/// +/// `Ok(None)` means the data ends before the OBU does, which a caller +/// framing a byte stream treats as "wait for more". +fn read_obu(data: &[u8]) -> Result> { + let Some(&first) = data.first() else { + return Ok(None); + }; + if first & 0x80 != 0 { + return Err(PixelForgeError::InvalidInput( + "AV1: obu_forbidden_bit is set".to_string(), + )); + } + let obu_extension_flag = first & 0x04 != 0; + let obu_has_size_field = first & 0x02 != 0; + let mut header = ObuHeader { + obu_type: ObuType::from_raw((first >> 3) & 0x0f), + obu_extension_flag, + obu_has_size_field, + temporal_id: 0, + spatial_id: 0, + }; + let mut header_len = 1; + if obu_extension_flag { + let Some(&ext) = data.get(1) else { + return Ok(None); + }; + header.temporal_id = ext >> 5; + header.spatial_id = (ext >> 3) & 0x03; + header_len = 2; + } + let payload_len = if obu_has_size_field { + let mut r = BitReader::new(&data[header_len..]); + let size = match r.leb128() { + Ok(size) => size, + // A leb128 cut short by the end of the data is incomplete, not + // invalid; one longer than eight bytes is invalid. + Err(_) if r.bits_left() == 0 => return Ok(None), + Err(e) => return Err(e), + }; + header_len += r.byte_position(); + usize::try_from(size).map_err(|_| { + PixelForgeError::InvalidInput("AV1: obu_size does not fit in memory".to_string()) + })? + } else { + // Without a size field the OBU runs to the end of the data, which is + // how a container may store the last OBU of a sample. + data.len() - header_len + }; + if data.len() - header_len < payload_len { + return Ok(None); + } + Ok(Some((header, header_len, payload_len))) +} + +/// Whether `data` parses as the start of an Annex B length-delimited stream +/// (`temporal_unit_size`, `frame_unit_size`, `obu_length`, then a temporal +/// delimiter), which low-overhead data never does: there, the first OBU is a +/// temporal delimiter `0x12 0x00`, which reads as a frame unit of size zero. +pub fn looks_like_annex_b(data: &[u8]) -> bool { + let mut r = BitReader::new(data); + let (Ok(tu_size), Ok(fu_size)) = (r.leb128(), r.leb128()) else { + return false; + }; + let Ok(obu_length) = r.leb128() else { + return false; + }; + let obu_start = r.byte_position(); + let Some(&first) = data.get(obu_start) else { + return false; + }; + // Sizes must nest: the OBU inside the frame unit inside the temporal + // unit, each also holding the size field of the one inside it. + if fu_size == 0 || fu_size >= tu_size || obu_length == 0 || obu_length >= fu_size { + return false; + } + let obu_type = (first >> 3) & 0x0f; + let has_ext = first & 0x04 != 0; + // A temporal delimiter has an empty payload, so the whole OBU is its + // header (plus a zero obu_size, if the encoder wrote one). + first & 0x80 == 0 && obu_type == 2 && obu_length <= 1 + has_ext as u64 + 1 +} + +/// Iterator over the OBUs of a low-overhead bitstream. +/// +/// Yields an error, then stops, on invalid data or on an OBU that runs past +/// the end of the buffer. +pub struct ObuIter<'a> { + data: &'a [u8], + pos: usize, + done: bool, +} + +/// Iterate over the OBUs in `data` (low-overhead format, section 5.2). +pub fn iter_obus(data: &[u8]) -> ObuIter<'_> { + ObuIter { + data, + pos: 0, + done: false, + } +} + +fn annex_b_error() -> PixelForgeError { + PixelForgeError::InvalidInput( + "AV1: input looks like an Annex B length-delimited stream; only the \ + low-overhead OBU format (section 5.2) is supported" + .to_string(), + ) +} + +impl<'a> Iterator for ObuIter<'a> { + type Item = Result>; + + fn next(&mut self) -> Option { + if self.done || self.pos >= self.data.len() { + return None; + } + let rest = &self.data[self.pos..]; + // Annex B data starts with a size, not an OBU header. The checks + // that would catch it, a forbidden bit or a missing size field, only + // fire on some first bytes, so look at the structure directly. + if self.pos == 0 && looks_like_annex_b(rest) { + self.done = true; + return Some(Err(annex_b_error())); + } + match read_obu(rest) { + Ok(Some((header, header_len, payload_len))) => { + let obu = Obu { + header, + offset: self.pos, + header_len, + payload: &rest[header_len..header_len + payload_len], + }; + self.pos += header_len + payload_len; + Some(Ok(obu)) + } + Ok(None) => { + self.done = true; + Some(Err(PixelForgeError::InvalidInput(format!( + "AV1: OBU at byte {} runs past the end of the data", + self.pos + )))) + } + Err(e) => { + self.done = true; + Some(Err(e)) + } + } + } +} + +/// Byte offsets at which each temporal unit begins in a low-overhead +/// byte stream: the offset of every temporal delimiter OBU. +/// +/// This is what lets a byte-stream feed decide where it may cut: everything +/// before the last offset is complete, and everything from it on may still be +/// waiting for OBUs that have not arrived. An OBU cut short by the end of the +/// data ends the scan without an error for the same reason. Invalid data, or +/// data in Annex B format, is an error. +pub fn temporal_unit_starts(data: &[u8]) -> Result> { + if looks_like_annex_b(data) { + return Err(annex_b_error()); + } + let mut starts = Vec::new(); + let mut pos = 0; + while pos < data.len() { + let rest = &data[pos..]; + // Without a size field an OBU would run to the end of whatever has + // arrived so far, which says nothing about where it really ends. + if rest[0] & 0x02 == 0 { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: OBU at byte {pos} has no obu_size, which a byte stream needs" + ))); + } + let Some((header, header_len, payload_len)) = read_obu(rest)? else { + break; + }; + if header.obu_type == ObuType::TemporalDelimiter { + starts.push(pos); + } + pos += header_len + payload_len; + } + Ok(starts) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reads_headers_and_sizes() { + // TD, then a padding OBU with an extension header and 2 bytes. + let data = [0x12, 0x00, 0x7e, 0b0100_1000, 0x02, 0xaa, 0xbb]; + let obus: Vec<_> = iter_obus(&data).collect::>().unwrap(); + assert_eq!(obus.len(), 2); + assert_eq!(obus[0].header.obu_type, ObuType::TemporalDelimiter); + assert_eq!(obus[0].payload.len(), 0); + let pad = &obus[1]; + assert_eq!(pad.header.obu_type, ObuType::Padding); + assert!(pad.header.obu_extension_flag); + assert_eq!(pad.header.temporal_id, 2); + assert_eq!(pad.header.spatial_id, 1); + assert_eq!(pad.offset, 2); + assert_eq!(pad.header_len, 3); + assert_eq!(pad.payload, &[0xaa, 0xbb]); + } + + #[test] + fn reports_truncated_and_forbidden() { + let truncated = [0x12, 0x00, 0x32, 0x05, 0x00]; + let results: Vec<_> = iter_obus(&truncated).collect(); + assert!(results[0].is_ok()); + assert!(results[1].is_err()); + assert_eq!(results.len(), 2); + + let forbidden = [0x92, 0x00]; + assert!(iter_obus(&forbidden).next().unwrap().is_err()); + } + + #[test] + fn finds_temporal_units_and_tolerates_a_cut_tail() { + // TD, padding(1), TD, padding cut short. + let data = [0x12, 0x00, 0x7a, 0x01, 0x00, 0x12, 0x00, 0x7a, 0x04, 0x00]; + assert_eq!(temporal_unit_starts(&data).unwrap(), vec![0, 5]); + } + + #[test] + fn detects_annex_b() { + // temporal_unit_size 3, frame_unit_size 2, obu_length 1, a TD header + // without a size field, then the next temporal unit. + let data = [0x03, 0x02, 0x01, 0x10, 0x03]; + assert!(looks_like_annex_b(&data)); + assert!(iter_obus(&data).next().unwrap().is_err()); + assert!(temporal_unit_starts(&data).is_err()); + // A low-overhead stream must not trip the check. + assert!(!looks_like_annex_b(&[0x12, 0x00, 0x0a, 0x0b])); + } + + #[test] + fn drops_obus_outside_the_operating_point() { + let mut header = ObuHeader { + obu_type: ObuType::Frame, + obu_extension_flag: true, + obu_has_size_field: true, + temporal_id: 1, + spatial_id: 0, + }; + // Temporal layer 0 and spatial layer 0 only. + let idc = 0x101; + assert!(!in_operating_point(&header, idc)); + header.temporal_id = 0; + assert!(in_operating_point(&header, idc)); + header.spatial_id = 1; + assert!(!in_operating_point(&header, idc)); + header.obu_type = ObuType::SequenceHeader; + assert!(in_operating_point(&header, idc)); + assert!(in_operating_point(&header, 0)); + } +} diff --git a/src/decoder/av1/params.rs b/src/decoder/av1/params.rs new file mode 100644 index 00000000..9fc62535 --- /dev/null +++ b/src/decoder/av1/params.rs @@ -0,0 +1,773 @@ +//! The coding-tool parameter blocks of the uncompressed header (AV1 +//! specification sections 5.9.12 to 5.9.30): quantization, segmentation, +//! delta q/lf, loop filter, CDEF, loop restoration, global motion and film +//! grain. +//! +//! Each block stores what Vulkan's matching `StdVideoAV1*` structure takes, +//! in the form the spec's syntax produces. Where Vulkan wants the coded value +//! rather than the derived one (the CDEF secondary strengths) the coded value +//! is kept and the field says so. + +use super::bits::BitReader; +use super::consts::*; +use super::sequence::SequenceHeader; +use crate::error::{PixelForgeError, Result}; + +/// `quantization_params()` (5.9.12). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct QuantizationParams { + pub base_q_idx: u8, + pub delta_q_y_dc: i8, + pub delta_q_u_dc: i8, + pub delta_q_u_ac: i8, + pub delta_q_v_dc: i8, + pub delta_q_v_ac: i8, + pub diff_uv_delta: bool, + pub using_qmatrix: bool, + pub qm_y: u8, + pub qm_u: u8, + pub qm_v: u8, +} + +fn read_delta_q(r: &mut BitReader) -> Result { + Ok(if r.flag()? { r.su(7)? as i8 } else { 0 }) +} + +pub(crate) fn parse_quantization( + r: &mut BitReader, + seq: &SequenceHeader, +) -> Result { + let cc = &seq.color_config; + let mut q = QuantizationParams { + base_q_idx: r.f(8)? as u8, + delta_q_y_dc: read_delta_q(r)?, + ..Default::default() + }; + if cc.num_planes() > 1 { + if cc.separate_uv_delta_q { + q.diff_uv_delta = r.flag()?; + } + q.delta_q_u_dc = read_delta_q(r)?; + q.delta_q_u_ac = read_delta_q(r)?; + if q.diff_uv_delta { + q.delta_q_v_dc = read_delta_q(r)?; + q.delta_q_v_ac = read_delta_q(r)?; + } else { + q.delta_q_v_dc = q.delta_q_u_dc; + q.delta_q_v_ac = q.delta_q_u_ac; + } + } + q.using_qmatrix = r.flag()?; + if q.using_qmatrix { + q.qm_y = r.f(4)? as u8; + q.qm_u = r.f(4)? as u8; + q.qm_v = if cc.separate_uv_delta_q { + r.f(4)? as u8 + } else { + q.qm_u + }; + } + Ok(q) +} + +/// `segmentation_params()` (5.9.14), with the feature tables in effect for +/// the frame (loaded from the primary reference frame when not updated). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct SegmentationParams { + pub segmentation_enabled: bool, + pub segmentation_update_map: bool, + pub segmentation_temporal_update: bool, + pub segmentation_update_data: bool, + /// `FeatureEnabled[segment][feature]`. + pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS], + /// `FeatureData[segment][feature]`, already clipped. + pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS], + pub seg_id_pre_skip: bool, + pub last_active_seg_id: u8, +} + +impl SegmentationParams { + /// `FeatureEnabled` as Vulkan takes it: one bitmask per segment, bit `j` + /// for feature `j`. + pub fn feature_enabled_masks(&self) -> [u8; MAX_SEGMENTS] { + self.feature_enabled.map(|features| { + features + .iter() + .enumerate() + .fold(0u8, |mask, (j, &on)| mask | ((on as u8) << j)) + }) + } + + /// `get_qindex(1, segment_id)` (7.12.2), the qindex ignoring delta q. + pub fn qindex(&self, base_q_idx: u8, segment_id: usize) -> u8 { + if self.segmentation_enabled && self.feature_enabled[segment_id][SEG_LVL_ALT_Q] { + let q = base_q_idx as i32 + self.feature_data[segment_id][SEG_LVL_ALT_Q] as i32; + q.clamp(0, 255) as u8 + } else { + base_q_idx + } + } +} + +const SEGMENTATION_FEATURE_BITS: [u32; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0]; +const SEGMENTATION_FEATURE_SIGNED: [bool; SEG_LVL_MAX] = + [true, true, true, true, true, false, false, false]; +const SEGMENTATION_FEATURE_MAX: [i32; SEG_LVL_MAX] = [ + 255, + MAX_LOOP_FILTER, + MAX_LOOP_FILTER, + MAX_LOOP_FILTER, + MAX_LOOP_FILTER, + 7, + 0, + 0, +]; + +/// Parse `segmentation_params()`. `previous` holds the feature tables from +/// `load_previous()` (or zeros after `setup_past_independence()`), which +/// stay in effect when the frame does not update them. +pub(crate) fn parse_segmentation( + r: &mut BitReader, + primary_ref_frame: u8, + previous: &SegmentationParams, +) -> Result { + let mut s = SegmentationParams { + segmentation_enabled: r.flag()?, + ..Default::default() + }; + if s.segmentation_enabled { + if primary_ref_frame == PRIMARY_REF_NONE { + s.segmentation_update_map = true; + s.segmentation_update_data = true; + } else { + s.segmentation_update_map = r.flag()?; + if s.segmentation_update_map { + s.segmentation_temporal_update = r.flag()?; + } + s.segmentation_update_data = r.flag()?; + } + if s.segmentation_update_data { + for i in 0..MAX_SEGMENTS { + for j in 0..SEG_LVL_MAX { + let enabled = r.flag()?; + s.feature_enabled[i][j] = enabled; + let mut clipped = 0; + if enabled { + let bits = SEGMENTATION_FEATURE_BITS[j]; + let limit = SEGMENTATION_FEATURE_MAX[j]; + clipped = if SEGMENTATION_FEATURE_SIGNED[j] { + r.su(1 + bits)?.clamp(-limit, limit) + } else { + (r.f(bits)? as i32).clamp(0, limit) + }; + } + s.feature_data[i][j] = clipped as i16; + } + } + } else { + s.feature_enabled = previous.feature_enabled; + s.feature_data = previous.feature_data; + } + } + for i in 0..MAX_SEGMENTS { + for j in 0..SEG_LVL_MAX { + if s.feature_enabled[i][j] { + s.last_active_seg_id = i as u8; + if j >= SEG_LVL_REF_FRAME { + s.seg_id_pre_skip = true; + } + } + } + } + Ok(s) +} + +/// `delta_q_params()` and `delta_lf_params()` (5.9.17, 5.9.18). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct DeltaParams { + pub delta_q_present: bool, + /// The coded `delta_q_res` (the shift is `1 << delta_q_res`). + pub delta_q_res: u8, + pub delta_lf_present: bool, + pub delta_lf_res: u8, + pub delta_lf_multi: bool, +} + +pub(crate) fn parse_delta_params( + r: &mut BitReader, + base_q_idx: u8, + allow_intrabc: bool, +) -> Result { + let mut d = DeltaParams::default(); + if base_q_idx > 0 { + d.delta_q_present = r.flag()?; + } + if d.delta_q_present { + d.delta_q_res = r.f(2)? as u8; + if !allow_intrabc { + d.delta_lf_present = r.flag()?; + } + if d.delta_lf_present { + d.delta_lf_res = r.f(2)? as u8; + d.delta_lf_multi = r.flag()?; + } + } + Ok(d) +} + +/// `loop_filter_params()` (5.9.11). The deltas are the values in effect for +/// the frame: loaded from the primary reference frame (or defaults), then +/// updated by whatever the frame codes. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct LoopFilterParams { + pub loop_filter_level: [u8; 4], + pub loop_filter_sharpness: u8, + pub loop_filter_delta_enabled: bool, + pub loop_filter_delta_update: bool, + /// Bit `i` set when `loop_filter_ref_deltas[i]` was coded (Vulkan form). + pub update_ref_delta: u8, + pub loop_filter_ref_deltas: [i8; TOTAL_REFS_PER_FRAME], + /// Bit `i` set when `loop_filter_mode_deltas[i]` was coded (Vulkan form). + pub update_mode_delta: u8, + pub loop_filter_mode_deltas: [i8; 2], +} + +pub(crate) fn parse_loop_filter( + r: &mut BitReader, + seq: &SequenceHeader, + coded_lossless: bool, + allow_intrabc: bool, + ref_deltas: [i8; TOTAL_REFS_PER_FRAME], + mode_deltas: [i8; 2], +) -> Result { + let mut lf = LoopFilterParams { + loop_filter_ref_deltas: ref_deltas, + loop_filter_mode_deltas: mode_deltas, + ..Default::default() + }; + if coded_lossless || allow_intrabc { + lf.loop_filter_ref_deltas = DEFAULT_LOOP_FILTER_REF_DELTAS; + lf.loop_filter_mode_deltas = [0, 0]; + return Ok(lf); + } + lf.loop_filter_level[0] = r.f(6)? as u8; + lf.loop_filter_level[1] = r.f(6)? as u8; + if seq.color_config.num_planes() > 1 + && (lf.loop_filter_level[0] != 0 || lf.loop_filter_level[1] != 0) + { + lf.loop_filter_level[2] = r.f(6)? as u8; + lf.loop_filter_level[3] = r.f(6)? as u8; + } + lf.loop_filter_sharpness = r.f(3)? as u8; + lf.loop_filter_delta_enabled = r.flag()?; + if lf.loop_filter_delta_enabled { + lf.loop_filter_delta_update = r.flag()?; + if lf.loop_filter_delta_update { + for i in 0..TOTAL_REFS_PER_FRAME { + if r.flag()? { + lf.update_ref_delta |= 1 << i; + lf.loop_filter_ref_deltas[i] = r.su(7)? as i8; + } + } + for i in 0..2 { + if r.flag()? { + lf.update_mode_delta |= 1 << i; + lf.loop_filter_mode_deltas[i] = r.su(7)? as i8; + } + } + } + } + Ok(lf) +} + +/// `cdef_params()` (5.9.19). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct CdefParams { + pub cdef_damping_minus_3: u8, + pub cdef_bits: u8, + pub cdef_y_pri_strength: [u8; 8], + /// Coded value (0..=3), as Vulkan takes it; the effective strength + /// maps 3 to 4. + pub cdef_y_sec_strength: [u8; 8], + pub cdef_uv_pri_strength: [u8; 8], + /// Coded value, like `cdef_y_sec_strength`. + pub cdef_uv_sec_strength: [u8; 8], +} + +pub(crate) fn parse_cdef( + r: &mut BitReader, + seq: &SequenceHeader, + coded_lossless: bool, + allow_intrabc: bool, +) -> Result { + let mut c = CdefParams::default(); + if coded_lossless || allow_intrabc || !seq.enable_cdef { + return Ok(c); + } + c.cdef_damping_minus_3 = r.f(2)? as u8; + c.cdef_bits = r.f(2)? as u8; + for i in 0..(1usize << c.cdef_bits) { + c.cdef_y_pri_strength[i] = r.f(4)? as u8; + c.cdef_y_sec_strength[i] = r.f(2)? as u8; + if seq.color_config.num_planes() > 1 { + c.cdef_uv_pri_strength[i] = r.f(4)? as u8; + c.cdef_uv_sec_strength[i] = r.f(2)? as u8; + } + } + Ok(c) +} + +/// `lr_params()` (5.9.20). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct LoopRestorationParams { + /// `FrameRestorationType` per plane: 0 none, 1 Wiener, 2 self-guided, + /// 3 switchable (the same numbering as Vulkan's enum). + pub frame_restoration_type: [u8; 3], + /// `LoopRestorationSize` per plane, in samples (32..=256). Vulkan takes + /// `log2(size) - 5`; see [`Self::size_log2_minus_5`]. + pub loop_restoration_size: [u16; 3], + pub uses_lr: bool, + pub uses_chroma_lr: bool, + /// The derived `lr_unit_shift` (after the extra shift). + pub lr_unit_shift: u8, + pub lr_uv_shift: u8, +} + +impl LoopRestorationParams { + pub fn size_log2_minus_5(&self) -> [u16; 3] { + self.loop_restoration_size + .map(|s| (s.max(32).trailing_zeros() as u16).saturating_sub(5)) + } +} + +/// Maps the coded `lr_type` to `FrameRestorationType`. +const REMAP_LR_TYPE: [u8; 4] = [0, 3, 1, 2]; + +pub(crate) fn parse_lr( + r: &mut BitReader, + seq: &SequenceHeader, + all_lossless: bool, + allow_intrabc: bool, +) -> Result { + let mut lr = LoopRestorationParams::default(); + if all_lossless || allow_intrabc || !seq.enable_restoration { + return Ok(lr); + } + for i in 0..seq.color_config.num_planes() { + let ty = REMAP_LR_TYPE[r.f(2)? as usize]; + lr.frame_restoration_type[i] = ty; + if ty != 0 { + lr.uses_lr = true; + if i > 0 { + lr.uses_chroma_lr = true; + } + } + } + if lr.uses_lr { + let mut shift = r.f(1)? as u8; + if seq.use_128x128_superblock { + shift += 1; + } else if shift != 0 { + shift += r.f(1)? as u8; + } + lr.lr_unit_shift = shift; + let size = RESTORATION_TILESIZE_MAX >> (2 - shift); + let cc = &seq.color_config; + if cc.subsampling_x && cc.subsampling_y && lr.uses_chroma_lr { + lr.lr_uv_shift = r.f(1)? as u8; + } + lr.loop_restoration_size = [size, size >> lr.lr_uv_shift, size >> lr.lr_uv_shift]; + } + Ok(lr) +} + +/// `global_motion_params()` (5.9.24), indexed by reference frame name. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GlobalMotionParams { + pub gm_type: [u8; TOTAL_REFS_PER_FRAME], + pub gm_params: [[i32; 6]; TOTAL_REFS_PER_FRAME], +} + +impl Default for GlobalMotionParams { + fn default() -> Self { + Self { + gm_type: [IDENTITY; TOTAL_REFS_PER_FRAME], + gm_params: [DEFAULT_GM_PARAMS; TOTAL_REFS_PER_FRAME], + } + } +} + +/// `decode_subexp(numSyms)` (5.9.27). +fn decode_subexp(r: &mut BitReader, num_syms: i32) -> Result { + let mut i = 0; + let mut mk = 0; + let k = 3; + loop { + let b2 = if i != 0 { k + i - 1 } else { k }; + let a = 1 << b2; + if num_syms <= mk + 3 * a { + return Ok(r.ns((num_syms - mk) as u32)? as i32 + mk); + } + if r.flag()? { + i += 1; + mk += a; + } else { + return Ok(r.f(b2 as u32)? as i32 + mk); + } + } +} + +/// `inverse_recenter(r, v)` (5.9.28). +fn inverse_recenter(r: i32, v: i32) -> i32 { + if v > 2 * r { + v + } else if v & 1 != 0 { + r - ((v + 1) >> 1) + } else { + r + (v >> 1) + } +} + +/// `decode_signed_subexp_with_ref(low, high, r)` (5.9.26). +fn decode_signed_subexp_with_ref( + reader: &mut BitReader, + low: i32, + high: i32, + r: i32, +) -> Result { + let mx = high - low; + let r = r - low; + let v = decode_subexp(reader, mx)?; + let x = if (r << 1) <= mx { + inverse_recenter(r, v) + } else { + mx - 1 - inverse_recenter(mx - 1 - r, v) + }; + Ok(x + low) +} + +/// Parse `global_motion_params()` against `prev`, the `PrevGmParams` from +/// the primary reference frame (or the defaults). +pub(crate) fn parse_global_motion( + r: &mut BitReader, + frame_is_intra: bool, + allow_high_precision_mv: bool, + prev: &[[i32; 6]; TOTAL_REFS_PER_FRAME], +) -> Result { + let mut gm = GlobalMotionParams::default(); + if frame_is_intra { + return Ok(gm); + } + #[allow(clippy::needless_range_loop)] // rf indexes three arrays. + for rf in LAST_FRAME..=ALTREF_FRAME { + let ty = if r.flag()? { + if r.flag()? { + ROTZOOM + } else if r.flag()? { + TRANSLATION + } else { + AFFINE + } + } else { + IDENTITY + }; + gm.gm_type[rf] = ty; + let mut read = |idx: usize, gm: &mut GlobalMotionParams| -> Result<()> { + let (abs_bits, prec_bits) = if idx < 2 { + if ty == TRANSLATION { + let hp = !allow_high_precision_mv as u32; + (GM_ABS_TRANS_ONLY_BITS - hp, GM_TRANS_ONLY_PREC_BITS - hp) + } else { + (GM_ABS_TRANS_BITS, GM_TRANS_PREC_BITS) + } + } else { + (GM_ABS_ALPHA_BITS, GM_ALPHA_PREC_BITS) + }; + let prec_diff = WARPEDMODEL_PREC_BITS - prec_bits; + let (round, sub) = if idx % 3 == 2 { + (1 << WARPEDMODEL_PREC_BITS, 1 << prec_bits) + } else { + (0, 0) + }; + let mx = 1 << abs_bits; + let reference = (prev[rf][idx] >> prec_diff) - sub; + let value = decode_signed_subexp_with_ref(r, -mx, mx + 1, reference)?; + gm.gm_params[rf][idx] = (value << prec_diff) + round; + Ok(()) + }; + if ty >= ROTZOOM { + read(2, &mut gm)?; + read(3, &mut gm)?; + if ty == AFFINE { + read(4, &mut gm)?; + read(5, &mut gm)?; + } else { + gm.gm_params[rf][4] = -gm.gm_params[rf][3]; + gm.gm_params[rf][5] = gm.gm_params[rf][2]; + } + } + if ty >= TRANSLATION { + read(0, &mut gm)?; + read(1, &mut gm)?; + } + } + Ok(gm) +} + +/// `film_grain_params()` (5.9.30). After `load_grain_params()` this holds +/// the referenced frame's parameters with this frame's `grain_seed`, which +/// is what Vulkan expects the application to supply. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FilmGrainParams { + pub apply_grain: bool, + pub grain_seed: u16, + /// As coded on this frame (Vulkan ignores it). + pub update_grain: bool, + /// As coded on this frame when `update_grain` is 0 (Vulkan ignores it). + pub film_grain_params_ref_idx: u8, + pub num_y_points: u8, + pub point_y_value: [u8; 14], + pub point_y_scaling: [u8; 14], + pub chroma_scaling_from_luma: bool, + pub num_cb_points: u8, + pub point_cb_value: [u8; 10], + pub point_cb_scaling: [u8; 10], + pub num_cr_points: u8, + pub point_cr_value: [u8; 10], + pub point_cr_scaling: [u8; 10], + pub grain_scaling_minus_8: u8, + pub ar_coeff_lag: u8, + pub ar_coeffs_y_plus_128: [u8; 24], + pub ar_coeffs_cb_plus_128: [u8; 25], + pub ar_coeffs_cr_plus_128: [u8; 25], + pub ar_coeff_shift_minus_6: u8, + pub grain_scale_shift: u8, + pub cb_mult: u8, + pub cb_luma_mult: u8, + pub cb_offset: u16, + pub cr_mult: u8, + pub cr_luma_mult: u8, + pub cr_offset: u16, + pub overlap_flag: bool, + pub clip_to_restricted_range: bool, +} + +impl Default for FilmGrainParams { + /// `reset_grain_params()`: everything zero. + fn default() -> Self { + Self { + apply_grain: false, + grain_seed: 0, + update_grain: false, + film_grain_params_ref_idx: 0, + num_y_points: 0, + point_y_value: [0; 14], + point_y_scaling: [0; 14], + chroma_scaling_from_luma: false, + num_cb_points: 0, + point_cb_value: [0; 10], + point_cb_scaling: [0; 10], + num_cr_points: 0, + point_cr_value: [0; 10], + point_cr_scaling: [0; 10], + grain_scaling_minus_8: 0, + ar_coeff_lag: 0, + ar_coeffs_y_plus_128: [0; 24], + ar_coeffs_cb_plus_128: [0; 25], + ar_coeffs_cr_plus_128: [0; 25], + ar_coeff_shift_minus_6: 0, + grain_scale_shift: 0, + cb_mult: 0, + cb_luma_mult: 0, + cb_offset: 0, + cr_mult: 0, + cr_luma_mult: 0, + cr_offset: 0, + overlap_flag: false, + clip_to_restricted_range: false, + } + } +} + +fn read_points(r: &mut BitReader, max: u8, values: &mut [u8], scalings: &mut [u8]) -> Result { + let n = r.f(4)? as u8; + if n > max { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: {n} film grain scaling points, at most {max} allowed" + ))); + } + for i in 0..n as usize { + values[i] = r.f(8)? as u8; + scalings[i] = r.f(8)? as u8; + } + Ok(n) +} + +/// What `film_grain_params()` needs to know about the frame. +pub(crate) struct GrainContext { + pub show_frame: bool, + pub showable_frame: bool, + pub is_inter_frame: bool, +} + +/// Parse `film_grain_params()`. `load` performs `load_grain_params(idx)`, +/// returning `None` when slot `idx` holds no valid frame. +pub(crate) fn parse_film_grain( + r: &mut BitReader, + seq: &SequenceHeader, + ctx: &GrainContext, + load: impl Fn(u8) -> Option, +) -> Result> { + if !seq.film_grain_params_present || (!ctx.show_frame && !ctx.showable_frame) { + return Ok(Ok(FilmGrainParams::default())); + } + let mut g = FilmGrainParams { + apply_grain: r.flag()?, + ..Default::default() + }; + if !g.apply_grain { + return Ok(Ok(FilmGrainParams::default())); + } + g.grain_seed = r.f(16)? as u16; + g.update_grain = if ctx.is_inter_frame { r.flag()? } else { true }; + if !g.update_grain { + let idx = r.f(3)? as u8; + let Some(mut loaded) = load(idx) else { + return Ok(Err(idx)); + }; + loaded.grain_seed = g.grain_seed; + loaded.update_grain = false; + loaded.film_grain_params_ref_idx = idx; + return Ok(Ok(loaded)); + } + g.num_y_points = read_points(r, 14, &mut g.point_y_value, &mut g.point_y_scaling)?; + let cc = &seq.color_config; + g.chroma_scaling_from_luma = if cc.mono_chrome { false } else { r.flag()? }; + if !(cc.mono_chrome + || g.chroma_scaling_from_luma + || (cc.subsampling_x && cc.subsampling_y && g.num_y_points == 0)) + { + g.num_cb_points = read_points(r, 10, &mut g.point_cb_value, &mut g.point_cb_scaling)?; + g.num_cr_points = read_points(r, 10, &mut g.point_cr_value, &mut g.point_cr_scaling)?; + } + g.grain_scaling_minus_8 = r.f(2)? as u8; + g.ar_coeff_lag = r.f(2)? as u8; + let num_pos_luma = 2 * g.ar_coeff_lag as usize * (g.ar_coeff_lag as usize + 1); + let num_pos_chroma = if g.num_y_points != 0 { + for i in 0..num_pos_luma { + g.ar_coeffs_y_plus_128[i] = r.f(8)? as u8; + } + num_pos_luma + 1 + } else { + num_pos_luma + }; + if g.chroma_scaling_from_luma || g.num_cb_points != 0 { + for i in 0..num_pos_chroma { + g.ar_coeffs_cb_plus_128[i] = r.f(8)? as u8; + } + } + if g.chroma_scaling_from_luma || g.num_cr_points != 0 { + for i in 0..num_pos_chroma { + g.ar_coeffs_cr_plus_128[i] = r.f(8)? as u8; + } + } + g.ar_coeff_shift_minus_6 = r.f(2)? as u8; + g.grain_scale_shift = r.f(2)? as u8; + if g.num_cb_points != 0 { + g.cb_mult = r.f(8)? as u8; + g.cb_luma_mult = r.f(8)? as u8; + g.cb_offset = r.f(9)? as u16; + } + if g.num_cr_points != 0 { + g.cr_mult = r.f(8)? as u8; + g.cr_luma_mult = r.f(8)? as u8; + g.cr_offset = r.f(9)? as u16; + } + g.overlap_flag = r.flag()?; + g.clip_to_restricted_range = r.flag()?; + Ok(Ok(g)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn inverse_recenter_matches_the_spec_table() { + // v above 2r passes through; odd v steps below r, even v above it. + assert_eq!(inverse_recenter(2, 5), 5); + assert_eq!(inverse_recenter(2, 1), 1); + assert_eq!(inverse_recenter(2, 3), 0); + assert_eq!(inverse_recenter(2, 2), 3); + assert_eq!(inverse_recenter(2, 0), 2); + } + + #[test] + fn subexp_reads_the_reference_back_for_a_zero_code() { + // decode_subexp reading 0 (more_bits=0, then f(3)=000) recenters to + // the reference itself. + let mut r = BitReader::new(&[0x00]); + let v = decode_signed_subexp_with_ref(&mut r, -4096, 4097, 17).unwrap(); + assert_eq!(v, 17); + assert_eq!(r.position(), 4); + } + + #[test] + fn loop_restoration_sizes_convert_to_vulkan_form() { + let lr = LoopRestorationParams { + loop_restoration_size: [256, 128, 64], + ..Default::default() + }; + assert_eq!(lr.size_log2_minus_5(), [3, 2, 1]); + } + + #[test] + fn film_grain_reuse_loads_the_reference_with_a_new_seed() { + let seq = SequenceHeader { + film_grain_params_present: true, + ..Default::default() + }; + let ctx = GrainContext { + show_frame: true, + showable_frame: true, + is_inter_frame: true, + }; + // apply_grain 1, grain_seed 0xbeef, update_grain 0, ref idx 3. + let bits = [0b1101_1111, 0b0111_0111, 0b1001_1000]; + let saved = FilmGrainParams { + apply_grain: true, + grain_seed: 1, + update_grain: true, + num_y_points: 5, + ..Default::default() + }; + let mut r = BitReader::new(&bits); + let loaded = parse_film_grain(&mut r, &seq, &ctx, |idx| { + assert_eq!(idx, 3); + Some(saved.clone()) + }) + .unwrap() + .unwrap(); + assert_eq!(r.position(), 21); + assert_eq!(loaded.grain_seed, 0xbeef); + assert_eq!(loaded.num_y_points, 5); + assert!(!loaded.update_grain); + assert_eq!(loaded.film_grain_params_ref_idx, 3); + + // An empty slot comes back as the slot to report missing. + let mut r = BitReader::new(&bits); + let missing = parse_film_grain(&mut r, &seq, &ctx, |_| None).unwrap(); + assert_eq!(missing, Err(3)); + } + + #[test] + fn feature_masks_pack_per_segment() { + let mut s = SegmentationParams::default(); + s.feature_enabled[0][0] = true; + s.feature_enabled[0][5] = true; + s.feature_enabled[7][7] = true; + let masks = s.feature_enabled_masks(); + assert_eq!(masks[0], 0b0010_0001); + assert_eq!(masks[7], 0b1000_0000); + } +} diff --git a/src/decoder/av1/parser.rs b/src/decoder/av1/parser.rs new file mode 100644 index 00000000..a2a8e77b --- /dev/null +++ b/src/decoder/av1/parser.rs @@ -0,0 +1,312 @@ +//! The OBU-level decoding process: which OBUs make up a frame, and when the +//! reference state moves on (AV1 specification sections 7.4, 7.5 and 7.20). + +use super::bits::BitReader; +use super::consts::TOTAL_REFS_PER_FRAME; +use super::frame_header::{FrameHeader, FrameType, HeaderOutcome, parse_uncompressed_header}; +use super::obu::{Obu, ObuType, in_operating_point}; +use super::refs::RefState; +use super::sequence::{SequenceHeader, parse_sequence_header}; +use super::tile::{TileGroup, TileLocation, parse_tile_group}; +use crate::error::{PixelForgeError, Result}; + +/// One frame as the OBUs describe it: the parsed header and where its data +/// sits. +/// +/// All offsets are relative to the start of the buffer the OBUs were +/// iterated from (see [`Obu::offset`]). Handing the driver that whole buffer +/// makes them usable as they are; handing it a sub-range means subtracting +/// the sub-range's start from each. +#[derive(Debug, Clone)] +pub struct Av1Frame { + pub header: FrameHeader, + /// The OBU that carried the header: `Frame`, `FrameHeader` or + /// `RedundantFrameHeader` (when the original was lost). + /// + /// This and the two fields below only check the parse in tests: the + /// decoder hands the driver tile data alone. + #[cfg_attr(not(test), allow(dead_code))] + pub header_obu_type: ObuType, + /// Offset of the first byte of that OBU (its `obu_header()`). + #[cfg_attr(not(test), allow(dead_code))] + pub frame_header_offset: usize, + /// Bytes of payload the header takes: `uncompressed_header()` plus its + /// byte alignment in a frame OBU, plus its trailing bits in a frame + /// header OBU. + #[cfg_attr(not(test), allow(dead_code))] + pub frame_header_bytes: usize, + /// The tile groups in order, which together cover every tile. Empty for + /// `show_existing_frame`. + pub tile_groups: Vec, +} + +impl Av1Frame { + /// Every tile of the frame, in tile order. + pub fn tiles(&self) -> impl Iterator + '_ { + self.tile_groups + .iter() + .flat_map(|tg| tg.tiles.iter().copied()) + } +} + +/// What one OBU produced. +#[derive(Debug)] +pub enum Av1Event { + /// A sequence header was parsed. Encoders repeat it at every key frame; + /// the decoder compares it with the one its session was built from. + SequenceHeader, + /// A frame is complete: all of its tile groups have arrived, or it is a + /// `show_existing_frame`. The caller decodes (or shows) it and then calls + /// [`Av1Parser::commit_frame`] or [`Av1Parser::discard_frame`]. + Frame(Box), + /// The next frame refers to reference slots that hold nothing (bit `i` + /// for slot `i`), as after joining a stream mid-way or losing a frame. + /// It cannot be decoded; its remaining OBUs are skipped, and the + /// decoder needs a key frame. + MissingReferences(u8), +} + +/// A frame whose header has arrived but not all of its tiles. +#[derive(Debug)] +struct InProgress { + frame: Av1Frame, + refs: Box, + next_tile: usize, +} + +/// A complete frame waiting for the caller to commit or discard it. +#[derive(Debug)] +struct Pending { + header: FrameHeader, + refs: Box, +} + +/// Stateful AV1 parser: feed it OBUs in order, get frames out. +#[derive(Debug, Default)] +pub struct Av1Parser { + sequence: Option, + refs: RefState, + /// `OrderHints` as the last committed frame left it. + order_hints: [u8; TOTAL_REFS_PER_FRAME], + current: Option, + pending: Option, + /// Set after `MissingReferences`, until the next frame header. + skipping: bool, +} + +impl Av1Parser { + pub fn new() -> Self { + Self::default() + } + + /// The sequence header in effect, once one has been seen. + pub fn sequence_header(&self) -> Option<&SequenceHeader> { + self.sequence.as_ref() + } + + /// The reference slots as of the last committed frame. While a frame is + /// pending, this is the state its header was parsed against, which is + /// what its references must be looked up in. + pub fn refs(&self) -> &RefState { + &self.refs + } + + /// Apply the reference frame update process for the pending frame, now + /// decoded into DPB slot `dpb_slot` (`None` if the caller does not track + /// it). For `show_existing_frame` of a key frame the shown slot's DPB + /// slot is used instead of `dpb_slot`. + pub fn commit_frame(&mut self, dpb_slot: Option) -> Result<()> { + let pending = self.pending.take().ok_or_else(|| { + PixelForgeError::InvalidInput("AV1: no frame waiting to be committed".to_string()) + })?; + let Some(seq) = &self.sequence else { + return Err(PixelForgeError::InvalidInput( + "AV1: frame without a sequence header".to_string(), + )); + }; + let header = &pending.header; + let mut refs = pending.refs; + refs.update(seq, header, dpb_slot); + self.refs = *refs; + if !header.show_existing_frame || header.frame_type == FrameType::Key { + self.order_hints = header.order_hints; + } + Ok(()) + } + + /// Drop the pending frame, as when it failed to decode. The slots it + /// would have refreshed no longer hold what the stream expects there, + /// so they become invalid, and frames that refer to them report missing + /// references until a key frame refreshes them. + pub fn discard_frame(&mut self) { + if let Some(pending) = self.pending.take() { + for (i, slot) in self.refs.slots.iter_mut().enumerate() { + if (pending.header.refresh_frame_flags >> i) & 1 != 0 { + slot.valid = false; + } + } + } + } + + /// Process one OBU. + pub fn parse_obu(&mut self, obu: &Obu) -> Result> { + match obu.header.obu_type { + ObuType::TemporalDelimiter => { + // SeenFrameHeader = 0. A frame still missing tiles here is + // broken; say so, but leave the parser ready for the next one. + self.skipping = false; + if self.current.take().is_some() { + return Err(PixelForgeError::InvalidInput( + "AV1: temporal unit ended before the frame's last tile group".to_string(), + )); + } + Ok(None) + } + ObuType::SequenceHeader => { + let seq = parse_sequence_header(obu.payload)?; + self.sequence = Some(seq); + Ok(Some(Av1Event::SequenceHeader)) + } + ObuType::FrameHeader | ObuType::RedundantFrameHeader | ObuType::Frame => { + if !self.selected(obu)? { + return Ok(None); + } + self.frame_header(obu) + } + ObuType::TileGroup => { + if !self.selected(obu)? || self.skipping { + return Ok(None); + } + let Some(current) = self.current.as_mut() else { + return Err(PixelForgeError::InvalidInput( + "AV1: tile group without a frame header".to_string(), + )); + }; + let tg = parse_tile_group( + obu.payload, + obu.payload_offset(), + ¤t.frame.header.tile_info, + )?; + self.add_tile_group(tg) + } + ObuType::Metadata | ObuType::TileList | ObuType::Padding | ObuType::Reserved(_) => { + Ok(None) + } + } + } + + /// Whether the OBU is in operating point 0; frame data before any + /// sequence header is an error. + fn selected(&self, obu: &Obu) -> Result { + let seq = self.sequence.as_ref().ok_or_else(|| { + PixelForgeError::InvalidInput("AV1: frame data before a sequence header".to_string()) + })?; + Ok(in_operating_point(&obu.header, seq.operating_point_idc())) + } + + fn frame_header(&mut self, obu: &Obu) -> Result> { + let is_frame_obu = obu.header.obu_type == ObuType::Frame; + if self.current.is_some() { + if is_frame_obu { + self.current = None; + return Err(PixelForgeError::InvalidInput( + "AV1: frame OBU before the previous frame's last tile group".to_string(), + )); + } + // frame_header_copy(): a repeat of the header in effect. + return Ok(None); + } + if self.skipping && obu.header.obu_type == ObuType::RedundantFrameHeader { + return Ok(None); + } + if self.pending.is_some() { + return Err(PixelForgeError::InvalidInput( + "AV1: new frame before the previous one was committed or discarded".to_string(), + )); + } + let seq = self.sequence.as_ref().expect("checked by selected()"); + + let mut r = BitReader::new(obu.payload); + let outcome = + parse_uncompressed_header(&mut r, seq, &self.refs, &obu.header, self.order_hints)?; + let (header, refs) = match outcome { + HeaderOutcome::Parsed(header, refs) => (header, refs), + HeaderOutcome::MissingReferences(slots) => { + self.skipping = true; + return Ok(Some(Av1Event::MissingReferences(slots))); + } + }; + self.skipping = false; + + if is_frame_obu { + if header.show_existing_frame { + return Err(PixelForgeError::InvalidInput( + "AV1: frame OBU with show_existing_frame".to_string(), + )); + } + r.byte_alignment()?; + } else { + r.trailing_bits(obu.payload.len())?; + } + let header_bytes = r.byte_position(); + + let frame = Av1Frame { + header: *header, + header_obu_type: obu.header.obu_type, + frame_header_offset: obu.offset, + frame_header_bytes: header_bytes, + tile_groups: Vec::new(), + }; + if frame.header.show_existing_frame { + self.pending = Some(Pending { + header: frame.header.clone(), + refs, + }); + return Ok(Some(Av1Event::Frame(Box::new(frame)))); + } + self.current = Some(InProgress { + frame, + refs, + next_tile: 0, + }); + if !is_frame_obu { + return Ok(None); + } + let current = self.current.as_ref().expect("just set"); + let tg = parse_tile_group( + &obu.payload[header_bytes..], + obu.payload_offset() + header_bytes, + ¤t.frame.header.tile_info, + ); + match tg { + Ok(tg) => self.add_tile_group(tg), + Err(e) => { + self.current = None; + Err(e) + } + } + } + + fn add_tile_group(&mut self, tg: TileGroup) -> Result> { + let current = self.current.as_mut().expect("caller checked"); + if tg.tg_start as usize != current.next_tile { + let expected = current.next_tile; + self.current = None; + return Err(PixelForgeError::InvalidInput(format!( + "AV1: tile group starts at tile {}, expected {expected}", + tg.tg_start + ))); + } + current.next_tile = tg.tg_end as usize + 1; + current.frame.tile_groups.push(tg); + if current.next_tile < current.frame.header.tile_info.num_tiles() { + return Ok(None); + } + let done = self.current.take().expect("checked above"); + self.pending = Some(Pending { + header: done.frame.header.clone(), + refs: done.refs, + }); + Ok(Some(Av1Event::Frame(Box::new(done.frame)))) + } +} diff --git a/src/decoder/av1/record.rs b/src/decoder/av1/record.rs new file mode 100644 index 00000000..7a3737f5 --- /dev/null +++ b/src/decoder/av1/record.rs @@ -0,0 +1,55 @@ +//! The AV1 structures of a decode command. + +use ash::vk; +use ash::vk::native::StdVideoDecodeAV1ReferenceInfo; + +use super::consts::REFS_PER_FRAME; +use super::std_info::StdPictureInfo; +use crate::decoder::record::RecordCodec; + +/// AV1's part of a [`DecodeRecord`](crate::decoder::record::DecodeRecord). +pub(super) struct Av1Record; + +/// What the AV1 picture info points at, besides the tile offsets the record +/// keeps. +#[derive(Default)] +pub(super) struct Av1Picture { + /// The Std picture info and its sub-structures. `None` only before the + /// first frame. + pub std: Option, + /// The DPB slot of each of LAST_FRAME..=ALTREF_FRAME, or -1. + pub reference_name_slot_indices: [i32; REFS_PER_FRAME], + /// Where the frame header OBU starts in the bitstream buffer. Always 0: + /// the buffer holds tile data alone. + pub frame_header_offset: u32, + /// The size of each tile, in the order of the record's tile offsets. + pub tile_sizes: Vec, +} + +impl RecordCodec for Av1Record { + type RefStd = StdVideoDecodeAV1ReferenceInfo; + type DpbSlot = vk::VideoDecodeAV1DpbSlotInfoKHR<'static>; + type Picture = Av1Picture; + type PictureInfo = vk::VideoDecodeAV1PictureInfoKHR<'static>; + + fn dpb_slot(std: &'static StdVideoDecodeAV1ReferenceInfo) -> Self::DpbSlot { + vk::VideoDecodeAV1DpbSlotInfoKHR::default().std_reference_info(std) + } + + fn picture_info( + picture: &'static mut Av1Picture, + offsets: &'static [u32], + ) -> Self::PictureInfo { + let std = picture + .std + .as_ref() + .expect("the picture info is filled before linking") + .get(); + vk::VideoDecodeAV1PictureInfoKHR::default() + .std_picture_info(std) + .reference_name_slot_indices(picture.reference_name_slot_indices) + .frame_header_offset(picture.frame_header_offset) + .tile_offsets(offsets) + .tile_sizes(&picture.tile_sizes) + } +} diff --git a/src/decoder/av1/refs.rs b/src/decoder/av1/refs.rs new file mode 100644 index 00000000..fcde1f94 --- /dev/null +++ b/src/decoder/av1/refs.rs @@ -0,0 +1,377 @@ +//! Reference frame state (AV1 specification sections 7.8, 7.20 and 7.21). +//! +//! AV1 keeps eight reference slots. Each slot remembers, besides the picture +//! itself, the frame's size, order hints and the coding-tool state that later +//! headers inherit from it (loop filter deltas, segmentation features, global +//! motion and film grain). The header parser reads this table; the reference +//! frame update process writes it once a frame is decoded. + +use super::consts::*; +use super::frame_header::{FrameHeader, FrameType}; +use super::params::FilmGrainParams; +use super::sequence::SequenceHeader; + +/// Everything one reference slot remembers. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RefSlot { + /// `RefValid`. + pub valid: bool, + /// `RefFrameId`. + pub frame_id: u32, + /// `RefFrameType`. + pub frame_type: FrameType, + /// `RefShowableFrame`: whether `show_existing_frame` may show it. + pub showable_frame: bool, + pub upscaled_width: u32, + pub frame_width: u32, + pub frame_height: u32, + pub render_width: u32, + pub render_height: u32, + pub mi_cols: u32, + pub mi_rows: u32, + pub bit_depth: u8, + pub subsampling_x: bool, + pub subsampling_y: bool, + /// `RefOrderHint`. + pub order_hint: u8, + /// `SavedOrderHints`, indexed by reference frame name. + pub saved_order_hints: [u8; TOTAL_REFS_PER_FRAME], + /// The frame's `RefFrameSignBias` as a bitmask indexed by reference + /// frame name, as Vulkan's reference info takes it. + pub ref_frame_sign_bias: u8, + /// Kept for Vulkan's reference info. + pub disable_frame_end_update_cdf: bool, + /// Kept for Vulkan's reference info. + pub segmentation_enabled: bool, + /// `SavedLoopFilterRefDeltas`. + pub loop_filter_ref_deltas: [i8; TOTAL_REFS_PER_FRAME], + /// `SavedLoopFilterModeDeltas`. + pub loop_filter_mode_deltas: [i8; 2], + /// `SavedFeatureEnabled`. + pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS], + /// `SavedFeatureData`. + pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS], + /// `SavedGmParams`. + pub gm_params: [[i32; 6]; TOTAL_REFS_PER_FRAME], + /// The saved film grain parameters (`load_grain_params()`). + pub film_grain: FilmGrainParams, + /// The Vulkan DPB slot holding this frame's picture, which the parser + /// never looks at: the decoder fills it in through + /// [`Av1Parser::commit_frame`](super::Av1Parser::commit_frame). + pub dpb_slot: Option, +} + +impl Default for RefSlot { + fn default() -> Self { + Self { + valid: false, + frame_id: 0, + frame_type: FrameType::Key, + showable_frame: false, + upscaled_width: 0, + frame_width: 0, + frame_height: 0, + render_width: 0, + render_height: 0, + mi_cols: 0, + mi_rows: 0, + bit_depth: 0, + subsampling_x: false, + subsampling_y: false, + order_hint: 0, + saved_order_hints: [0; TOTAL_REFS_PER_FRAME], + ref_frame_sign_bias: 0, + disable_frame_end_update_cdf: false, + segmentation_enabled: false, + loop_filter_ref_deltas: DEFAULT_LOOP_FILTER_REF_DELTAS, + loop_filter_mode_deltas: [0; 2], + feature_enabled: [[false; SEG_LVL_MAX]; MAX_SEGMENTS], + feature_data: [[0; SEG_LVL_MAX]; MAX_SEGMENTS], + gm_params: [DEFAULT_GM_PARAMS; TOTAL_REFS_PER_FRAME], + film_grain: FilmGrainParams::default(), + dpb_slot: None, + } + } +} + +/// The eight reference slots. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct RefState { + pub slots: [RefSlot; NUM_REF_FRAMES], +} + +impl RefState { + /// The reference frame update process (7.20): store the decoded frame + /// in every slot `refresh_frame_flags` selects, with `dpb_slot` as the + /// picture's location. + /// + /// For a key frame shown with `show_existing_frame`, the loading process + /// (7.21) makes the frame the current one first, so every slot becomes a + /// copy of the shown slot, DPB location included. + pub fn update(&mut self, seq: &SequenceHeader, header: &FrameHeader, dpb_slot: Option) { + if header.show_existing_frame { + if header.frame_type == FrameType::Key { + let mut shown = self.slots[header.frame_to_show_map_idx as usize].clone(); + // A key frame may be shown this way at most once. + shown.showable_frame = false; + for i in 0..NUM_REF_FRAMES { + if (header.refresh_frame_flags >> i) & 1 != 0 { + self.slots[i] = shown.clone(); + } + } + } + return; + } + + let size = &header.frame_size; + let slot = RefSlot { + valid: true, + frame_id: header.current_frame_id, + frame_type: header.frame_type, + showable_frame: header.showable_frame, + upscaled_width: size.upscaled_width, + frame_width: size.frame_width, + frame_height: size.frame_height, + render_width: size.render_width, + render_height: size.render_height, + mi_cols: size.mi_cols, + mi_rows: size.mi_rows, + bit_depth: seq.color_config.bit_depth, + subsampling_x: seq.color_config.subsampling_x, + subsampling_y: seq.color_config.subsampling_y, + order_hint: header.order_hint, + saved_order_hints: header.order_hints, + ref_frame_sign_bias: header.ref_frame_sign_bias_mask(), + disable_frame_end_update_cdf: header.disable_frame_end_update_cdf, + segmentation_enabled: header.segmentation.segmentation_enabled, + loop_filter_ref_deltas: header.loop_filter.loop_filter_ref_deltas, + loop_filter_mode_deltas: header.loop_filter.loop_filter_mode_deltas, + feature_enabled: header.segmentation.feature_enabled, + feature_data: header.segmentation.feature_data, + gm_params: header.global_motion.gm_params, + film_grain: header.film_grain.clone(), + dpb_slot, + }; + for i in 0..NUM_REF_FRAMES { + if (header.refresh_frame_flags >> i) & 1 != 0 { + self.slots[i] = slot.clone(); + } + } + } + + /// Whether any slot still refers to DPB slot `dpb_slot`, which tells the + /// decoder when a picture can be reused. + pub fn dpb_slot_in_use(&self, dpb_slot: u8) -> bool { + self.slots.iter().any(|s| s.dpb_slot == Some(dpb_slot)) + } +} + +/// `get_relative_dist(a, b)` (7.12.3). +pub fn relative_dist(seq: &SequenceHeader, a: u8, b: u8) -> i32 { + if !seq.enable_order_hint || seq.order_hint_bits == 0 { + return 0; + } + let diff = a as i32 - b as i32; + let m = 1 << (seq.order_hint_bits - 1); + (diff & (m - 1)) - (diff & m) +} + +/// The set_frame_refs process (7.8): derive all seven `ref_frame_idx` from +/// the two coded with `frame_refs_short_signaling`. +pub fn set_frame_refs( + seq: &SequenceHeader, + refs: &RefState, + order_hint: u8, + last_frame_idx: u8, + gold_frame_idx: u8, +) -> [u8; REFS_PER_FRAME] { + let mut ref_frame_idx = [-1i32; REFS_PER_FRAME]; + ref_frame_idx[0] = last_frame_idx as i32; + ref_frame_idx[GOLDEN_FRAME - LAST_FRAME] = gold_frame_idx as i32; + let mut used = [false; NUM_REF_FRAMES]; + used[last_frame_idx as usize] = true; + used[gold_frame_idx as usize] = true; + + let cur_frame_hint = 1i32 << (seq.order_hint_bits.max(1) - 1); + let shifted: [i32; NUM_REF_FRAMES] = std::array::from_fn(|i| { + cur_frame_hint + relative_dist(seq, refs.slots[i].order_hint, order_hint) + }); + + // Latest or earliest unused backward (future) reference, or latest + // forward (past) one. + let find = |used: &[bool; NUM_REF_FRAMES], backward: bool, latest: bool| -> Option { + let mut best: Option<(usize, i32)> = None; + for (i, &hint) in shifted.iter().enumerate() { + let side = if backward { + hint >= cur_frame_hint + } else { + hint < cur_frame_hint + }; + if used[i] || !side { + continue; + } + let better = match best { + None => true, + Some((_, b)) if latest => hint >= b, + Some((_, b)) => hint < b, + }; + if better { + best = Some((i, hint)); + } + } + best.map(|(i, _)| i) + }; + + if let Some(r) = find(&used, true, true) { + ref_frame_idx[ALTREF_FRAME - LAST_FRAME] = r as i32; + used[r] = true; + } + if let Some(r) = find(&used, true, false) { + ref_frame_idx[BWDREF_FRAME - LAST_FRAME] = r as i32; + used[r] = true; + } + if let Some(r) = find(&used, true, false) { + ref_frame_idx[ALTREF2_FRAME - LAST_FRAME] = r as i32; + used[r] = true; + } + const REF_FRAME_LIST: [usize; REFS_PER_FRAME - 2] = [ + LAST2_FRAME, + LAST3_FRAME, + BWDREF_FRAME, + ALTREF2_FRAME, + ALTREF_FRAME, + ]; + for ref_frame in REF_FRAME_LIST { + if ref_frame_idx[ref_frame - LAST_FRAME] < 0 + && let Some(r) = find(&used, false, true) + { + ref_frame_idx[ref_frame - LAST_FRAME] = r as i32; + used[r] = true; + } + } + + // Whatever is left gets the earliest frame of all. + let mut earliest: Option<(usize, i32)> = None; + for (i, &hint) in shifted.iter().enumerate() { + if earliest.is_none_or(|(_, e)| hint < e) { + earliest = Some((i, hint)); + } + } + let fallback = earliest.map_or(0, |(i, _)| i as i32); + ref_frame_idx.map(|idx| if idx < 0 { fallback } else { idx } as u8) +} + +/// The frame id check of `mark_ref_frames(idLen)` (5.9.4): invalidate the +/// slots whose frame id cannot precede `current_frame_id`. +pub fn mark_ref_frames(seq: &SequenceHeader, refs: &mut RefState, current_frame_id: u32) { + let id_len = seq.frame_id_length(); + let diff_len = seq.delta_frame_id_length_minus_2 as u32 + 2; + for slot in refs.slots.iter_mut() { + let id = slot.frame_id; + let stale = if current_frame_id > (1 << diff_len) { + id > current_frame_id || id < current_frame_id - (1 << diff_len) + } else { + id > current_frame_id && id < (1 << id_len) + current_frame_id - (1 << diff_len) + }; + if stale { + slot.valid = false; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn seq_with_order_hints(bits: u8) -> SequenceHeader { + SequenceHeader { + enable_order_hint: true, + order_hint_bits: bits, + ..Default::default() + } + } + + #[test] + fn relative_dist_wraps() { + let seq = seq_with_order_hints(7); + assert_eq!(relative_dist(&seq, 5, 3), 2); + assert_eq!(relative_dist(&seq, 3, 5), -2); + // 127 is one before 0 with 7-bit hints. + assert_eq!(relative_dist(&seq, 127, 0), -1); + assert_eq!(relative_dist(&seq, 0, 127), 1); + assert_eq!(relative_dist(&SequenceHeader::default(), 9, 1), 0); + } + + #[test] + fn set_frame_refs_orders_past_and_future() { + // Current frame 10; slots hold 2, 4, 6, 8 (past) and 12, 14, 16 and + // 9 (one more past frame). + let seq = seq_with_order_hints(7); + let mut refs = RefState::default(); + for (slot, hint) in [2u8, 4, 6, 8, 12, 14, 16, 9].iter().enumerate() { + refs.slots[slot].order_hint = *hint; + } + // LAST is slot 3 (8), GOLDEN slot 0 (2). + let idx = set_frame_refs(&seq, &refs, 10, 3, 0); + assert_eq!(idx[0], 3, "LAST as coded"); + assert_eq!(idx[GOLDEN_FRAME - LAST_FRAME], 0, "GOLDEN as coded"); + assert_eq!( + idx[ALTREF_FRAME - LAST_FRAME], + 6, + "ALTREF: latest future (16)" + ); + assert_eq!( + idx[BWDREF_FRAME - LAST_FRAME], + 4, + "BWDREF: earliest future (12)" + ); + assert_eq!( + idx[ALTREF2_FRAME - LAST_FRAME], + 5, + "ALTREF2: next future (14)" + ); + assert_eq!( + idx[LAST2_FRAME - LAST_FRAME], + 7, + "LAST2: latest past left (9)" + ); + assert_eq!(idx[LAST3_FRAME - LAST_FRAME], 2, "LAST3: next past (6)"); + } + + #[test] + fn set_frame_refs_fills_gaps_with_the_earliest_frame() { + // Every slot in the past and only three distinct ones: the rest of + // the list falls back to the earliest frame. + let seq = seq_with_order_hints(7); + let mut refs = RefState::default(); + for (slot, hint) in [5u8, 3, 1, 1, 1, 1, 1, 1].iter().enumerate() { + refs.slots[slot].order_hint = *hint; + } + let idx = set_frame_refs(&seq, &refs, 6, 0, 1); + assert_eq!(idx[0], 0); + assert_eq!(idx[GOLDEN_FRAME - LAST_FRAME], 1); + // No future frames: BWDREF/ALTREF2/ALTREF take the remaining past + // frames in latest-first order, then the earliest frame (slot 2). + assert!(idx.iter().all(|&i| i < 8)); + assert_eq!(idx[LAST2_FRAME - LAST_FRAME], 7); + } + + #[test] + fn mark_ref_frames_drops_ids_from_the_future() { + let seq = SequenceHeader { + frame_id_numbers_present_flag: true, + delta_frame_id_length_minus_2: 2, // diffLen 4 + additional_frame_id_length_minus_1: 3, // idLen 8 + ..Default::default() + }; + let mut refs = RefState::default(); + for (i, slot) in refs.slots.iter_mut().enumerate() { + slot.valid = true; + slot.frame_id = [30, 20, 14, 31, 40, 25, 15, 29][i]; + } + // Current id 30: valid ids are 14..=30. + mark_ref_frames(&seq, &mut refs, 30); + let valid: Vec = refs.slots.iter().map(|s| s.valid).collect(); + assert_eq!(valid, [true, true, true, false, false, true, true, true]); + } +} diff --git a/src/decoder/av1/sequence.rs b/src/decoder/av1/sequence.rs new file mode 100644 index 00000000..c7032f49 --- /dev/null +++ b/src/decoder/av1/sequence.rs @@ -0,0 +1,355 @@ +//! Sequence header OBU (AV1 specification section 5.5). +//! +//! Every syntax element is kept, including those Vulkan does not take, so a +//! change in any of them can be detected by comparing two headers. + +use super::bits::BitReader; +use super::consts::{SELECT_INTEGER_MV, SELECT_SCREEN_CONTENT_TOOLS}; +use crate::error::{PixelForgeError, Result}; + +/// `timing_info()` (section 5.5.3). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TimingInfo { + pub num_units_in_display_tick: u32, + pub time_scale: u32, + pub equal_picture_interval: bool, + /// Zero unless `equal_picture_interval`. + pub num_ticks_per_picture_minus_1: u32, +} + +/// `decoder_model_info()` (section 5.5.4). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct DecoderModelInfo { + pub buffer_delay_length_minus_1: u8, + pub num_units_in_decoding_tick: u32, + pub buffer_removal_time_length_minus_1: u8, + pub frame_presentation_time_length_minus_1: u8, +} + +/// One entry of the operating point loop. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct OperatingPoint { + pub operating_point_idc: u16, + pub seq_level_idx: u8, + pub seq_tier: u8, + pub decoder_model_present_for_this_op: bool, + /// `operating_parameters_info()`, when the decoder model is present. + pub decoder_buffer_delay: u32, + pub encoder_buffer_delay: u32, + pub low_delay_mode_flag: bool, + pub initial_display_delay_present_for_this_op: bool, + pub initial_display_delay_minus_1: u8, +} + +/// `color_config()` (section 5.5.2), with the derived `BitDepth`. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ColorConfig { + pub high_bitdepth: bool, + pub twelve_bit: bool, + /// `BitDepth`: 8, 10 or 12. + pub bit_depth: u8, + pub mono_chrome: bool, + pub color_description_present_flag: bool, + pub color_primaries: u8, + pub transfer_characteristics: u8, + pub matrix_coefficients: u8, + pub color_range: bool, + pub subsampling_x: bool, + pub subsampling_y: bool, + pub chroma_sample_position: u8, + pub separate_uv_delta_q: bool, +} + +impl ColorConfig { + /// `NumPlanes`. + pub fn num_planes(&self) -> usize { + if self.mono_chrome { 1 } else { 3 } + } +} + +/// Parsed sequence header OBU. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct SequenceHeader { + pub seq_profile: u8, + pub still_picture: bool, + pub reduced_still_picture_header: bool, + pub timing_info: Option, + pub decoder_model_info: Option, + pub initial_display_delay_present_flag: bool, + /// At least one entry; the parser decodes operating point 0. + pub operating_points: Vec, + pub frame_width_bits_minus_1: u8, + pub frame_height_bits_minus_1: u8, + pub max_frame_width_minus_1: u16, + pub max_frame_height_minus_1: u16, + pub frame_id_numbers_present_flag: bool, + pub delta_frame_id_length_minus_2: u8, + pub additional_frame_id_length_minus_1: u8, + pub use_128x128_superblock: bool, + pub enable_filter_intra: bool, + pub enable_intra_edge_filter: bool, + pub enable_interintra_compound: bool, + pub enable_masked_compound: bool, + pub enable_warped_motion: bool, + pub enable_dual_filter: bool, + pub enable_order_hint: bool, + pub enable_jnt_comp: bool, + pub enable_ref_frame_mvs: bool, + pub seq_choose_screen_content_tools: bool, + /// 0, 1, or `SELECT_SCREEN_CONTENT_TOOLS` (2). + pub seq_force_screen_content_tools: u8, + pub seq_choose_integer_mv: bool, + /// 0, 1, or `SELECT_INTEGER_MV` (2). + pub seq_force_integer_mv: u8, + /// `OrderHintBits`: 0 without order hints, else 1..=8. + pub order_hint_bits: u8, + pub enable_superres: bool, + pub enable_cdef: bool, + pub enable_restoration: bool, + pub color_config: ColorConfig, + pub film_grain_params_present: bool, +} + +impl SequenceHeader { + /// `OperatingPointIdc` of operating point 0, the one this parser decodes. + pub fn operating_point_idc(&self) -> u16 { + self.operating_points + .first() + .map_or(0, |op| op.operating_point_idc) + } + + /// `order_hint_bits_minus_1` as Vulkan takes it (0 when absent). + pub fn order_hint_bits_minus_1(&self) -> u8 { + self.order_hint_bits.saturating_sub(1) + } + + /// `idLen`, the width of frame ids, when they are present. + pub fn frame_id_length(&self) -> u32 { + self.additional_frame_id_length_minus_1 as u32 + + self.delta_frame_id_length_minus_2 as u32 + + 3 + } + + /// `equal_picture_interval`, false without timing info. + pub fn equal_picture_interval(&self) -> bool { + self.timing_info + .as_ref() + .is_some_and(|t| t.equal_picture_interval) + } + + pub fn max_frame_width(&self) -> u32 { + self.max_frame_width_minus_1 as u32 + 1 + } + + pub fn max_frame_height(&self) -> u32 { + self.max_frame_height_minus_1 as u32 + 1 + } +} + +/// Parse a sequence header OBU payload, including its trailing bits. +pub fn parse_sequence_header(payload: &[u8]) -> Result { + let mut r = BitReader::new(payload); + let mut seq = SequenceHeader { + seq_profile: r.f(3)? as u8, + still_picture: r.flag()?, + reduced_still_picture_header: r.flag()?, + ..Default::default() + }; + if seq.seq_profile > 2 { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: reserved seq_profile {}", + seq.seq_profile + ))); + } + + if seq.reduced_still_picture_header { + seq.operating_points.push(OperatingPoint { + seq_level_idx: r.f(5)? as u8, + ..Default::default() + }); + } else { + if r.flag()? { + let mut timing = TimingInfo { + num_units_in_display_tick: r.f(32)?, + time_scale: r.f(32)?, + equal_picture_interval: r.flag()?, + num_ticks_per_picture_minus_1: 0, + }; + if timing.equal_picture_interval { + timing.num_ticks_per_picture_minus_1 = r.uvlc()?; + } + seq.timing_info = Some(timing); + if r.flag()? { + seq.decoder_model_info = Some(DecoderModelInfo { + buffer_delay_length_minus_1: r.f(5)? as u8, + num_units_in_decoding_tick: r.f(32)?, + buffer_removal_time_length_minus_1: r.f(5)? as u8, + frame_presentation_time_length_minus_1: r.f(5)? as u8, + }); + } + } + seq.initial_display_delay_present_flag = r.flag()?; + let count = r.f(5)? + 1; + for _ in 0..count { + let mut op = OperatingPoint { + operating_point_idc: r.f(12)? as u16, + seq_level_idx: r.f(5)? as u8, + ..Default::default() + }; + if op.seq_level_idx > 7 { + op.seq_tier = r.f(1)? as u8; + } + if let Some(model) = &seq.decoder_model_info { + op.decoder_model_present_for_this_op = r.flag()?; + if op.decoder_model_present_for_this_op { + let n = model.buffer_delay_length_minus_1 as u32 + 1; + op.decoder_buffer_delay = r.f(n)?; + op.encoder_buffer_delay = r.f(n)?; + op.low_delay_mode_flag = r.flag()?; + } + } + if seq.initial_display_delay_present_flag { + op.initial_display_delay_present_for_this_op = r.flag()?; + if op.initial_display_delay_present_for_this_op { + op.initial_display_delay_minus_1 = r.f(4)? as u8; + } + } + seq.operating_points.push(op); + } + } + + seq.frame_width_bits_minus_1 = r.f(4)? as u8; + seq.frame_height_bits_minus_1 = r.f(4)? as u8; + seq.max_frame_width_minus_1 = r.f(seq.frame_width_bits_minus_1 as u32 + 1)? as u16; + seq.max_frame_height_minus_1 = r.f(seq.frame_height_bits_minus_1 as u32 + 1)? as u16; + if !seq.reduced_still_picture_header { + seq.frame_id_numbers_present_flag = r.flag()?; + } + if seq.frame_id_numbers_present_flag { + seq.delta_frame_id_length_minus_2 = r.f(4)? as u8; + seq.additional_frame_id_length_minus_1 = r.f(3)? as u8; + if seq.frame_id_length() > 16 { + // The spec caps idLen at 16 bits. + return Err(PixelForgeError::InvalidInput( + "AV1: frame id length exceeds 16 bits".to_string(), + )); + } + } + seq.use_128x128_superblock = r.flag()?; + seq.enable_filter_intra = r.flag()?; + seq.enable_intra_edge_filter = r.flag()?; + + if seq.reduced_still_picture_header { + seq.seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS; + seq.seq_force_integer_mv = SELECT_INTEGER_MV; + } else { + seq.enable_interintra_compound = r.flag()?; + seq.enable_masked_compound = r.flag()?; + seq.enable_warped_motion = r.flag()?; + seq.enable_dual_filter = r.flag()?; + seq.enable_order_hint = r.flag()?; + if seq.enable_order_hint { + seq.enable_jnt_comp = r.flag()?; + seq.enable_ref_frame_mvs = r.flag()?; + } + seq.seq_choose_screen_content_tools = r.flag()?; + seq.seq_force_screen_content_tools = if seq.seq_choose_screen_content_tools { + SELECT_SCREEN_CONTENT_TOOLS + } else { + r.f(1)? as u8 + }; + seq.seq_force_integer_mv = if seq.seq_force_screen_content_tools > 0 { + seq.seq_choose_integer_mv = r.flag()?; + if seq.seq_choose_integer_mv { + SELECT_INTEGER_MV + } else { + r.f(1)? as u8 + } + } else { + SELECT_INTEGER_MV + }; + if seq.enable_order_hint { + seq.order_hint_bits = r.f(3)? as u8 + 1; + } + } + + seq.enable_superres = r.flag()?; + seq.enable_cdef = r.flag()?; + seq.enable_restoration = r.flag()?; + seq.color_config = parse_color_config(&mut r, seq.seq_profile)?; + seq.film_grain_params_present = r.flag()?; + r.trailing_bits(payload.len())?; + Ok(seq) +} + +/// Color primaries, transfer and matrix values the parser branches on. +const CP_BT_709: u8 = 1; +const CP_UNSPECIFIED: u8 = 2; +const TC_UNSPECIFIED: u8 = 2; +const TC_SRGB: u8 = 13; +const MC_IDENTITY: u8 = 0; +const MC_UNSPECIFIED: u8 = 2; + +fn parse_color_config(r: &mut BitReader, seq_profile: u8) -> Result { + let mut cc = ColorConfig { + high_bitdepth: r.flag()?, + ..Default::default() + }; + cc.bit_depth = if seq_profile == 2 && cc.high_bitdepth { + cc.twelve_bit = r.flag()?; + if cc.twelve_bit { 12 } else { 10 } + } else if cc.high_bitdepth { + 10 + } else { + 8 + }; + cc.mono_chrome = if seq_profile == 1 { false } else { r.flag()? }; + cc.color_description_present_flag = r.flag()?; + if cc.color_description_present_flag { + cc.color_primaries = r.f(8)? as u8; + cc.transfer_characteristics = r.f(8)? as u8; + cc.matrix_coefficients = r.f(8)? as u8; + } else { + cc.color_primaries = CP_UNSPECIFIED; + cc.transfer_characteristics = TC_UNSPECIFIED; + cc.matrix_coefficients = MC_UNSPECIFIED; + } + + if cc.mono_chrome { + cc.color_range = r.flag()?; + cc.subsampling_x = true; + cc.subsampling_y = true; + // CSP_UNKNOWN, and no separate_uv_delta_q to read. + return Ok(cc); + } + if cc.color_primaries == CP_BT_709 + && cc.transfer_characteristics == TC_SRGB + && cc.matrix_coefficients == MC_IDENTITY + { + cc.color_range = true; + } else { + cc.color_range = r.flag()?; + match seq_profile { + 0 => { + cc.subsampling_x = true; + cc.subsampling_y = true; + } + 1 => {} + _ => { + if cc.bit_depth == 12 { + cc.subsampling_x = r.flag()?; + if cc.subsampling_x { + cc.subsampling_y = r.flag()?; + } + } else { + cc.subsampling_x = true; + } + } + } + if cc.subsampling_x && cc.subsampling_y { + cc.chroma_sample_position = r.f(2)? as u8; + } + } + cc.separate_uv_delta_q = r.flag()?; + Ok(cc) +} diff --git a/src/decoder/av1/session.rs b/src/decoder/av1/session.rs new file mode 100644 index 00000000..5a5e271c --- /dev/null +++ b/src/decoder/av1/session.rs @@ -0,0 +1,504 @@ +//! The AV1 Vulkan decode session: lazy stream-driven setup and per-frame +//! record/submit. +//! +//! One unit of input is a temporal unit, which shows exactly one frame but can +//! decode more than one: a hidden frame (`show_frame` 0) that a later +//! `show_existing_frame` shows, followed by the frame shown now. Each frame is +//! parsed only once the one before it is committed, since its header depends +//! on the reference state that frame leaves behind. + +use ash::vk; +use ash::vk::TaggedStructure; +use tracing::debug; + +use super::frame_header::FrameType; +use super::obu::{ObuType, iter_obus, temporal_unit_starts}; +use super::parser::{Av1Event, Av1Frame, Av1Parser}; +use super::record::{Av1Picture, Av1Record}; +use super::sequence::SequenceHeader; +use super::std_info::{ + StdPictureInfo, StdSequenceHeader, reference_name_slot_indices, std_reference_info, +}; +use crate::decoder::DecodeConfig; +use crate::decoder::common::DecoderCommon; +use crate::decoder::driver::{CodecDecoder, Decoded}; +use crate::decoder::frames::DecodedPicture; +use crate::decoder::pipeline::DECODE_PIPELINE_DEPTH; +use crate::decoder::plan::StreamNeeds; +use crate::decoder::profile::DecodeProfile; +use crate::decoder::record::{DecodeRecord, LinkParams}; +use crate::encoder::{BitDepth, ChromaFormat}; +use crate::error::{PixelForgeError, Result}; +use crate::vulkan::VideoContext; + +/// The most DPB slots the AV1 decoder uses: the 8 reference frames, the frame +/// being decoded, and spare slots for frames held for output. +const MAX_DPB_SLOTS: u32 = 17; + +/// AV1 decoder. +/// +/// Owns only what is specific to AV1: the OBU parser with its reference state. +/// Everything Vulkan lives in [`DecoderCommon`]. +pub(crate) struct Av1Decoder { + common: DecoderCommon, + + /// How many DPB slots to reserve beyond the stream's needs, so the caller + /// can hold decoded frames while decoding continues. + output_depth: usize, + + parser: Av1Parser, + /// The sequence header the current session was built from. + active: Option, + /// The Std sequence header the current session parameters were created + /// from, kept for as long as they exist. + /// + /// NVIDIA (615.78.08) does not copy it when the parameters are created: + /// it reads it again at every decode. Freed, its memory is reused, and the + /// decode reads whatever is there now. Nothing fails; the chroma of every + /// inter frame just comes out slightly wrong, which is how this was found. + /// Boxed, so moving the decoder does not move it. + std_sequence: Option>, + /// DPB slots in the current session. + slot_count: usize, + /// Which DPB slots hold a frame decoded with film grain, whose output is + /// in the slot's grain image. A `show_existing_frame` of it shows that + /// image again. + grain_applied: [bool; MAX_DPB_SLOTS as usize], + + /// Shown frames since the last key frame, which is the display order: AV1 + /// shows frames in the order the stream gives them. + shown_since_key: i32, + + /// What each pipeline slot's decode command was recorded from, indexed like + /// the pipeline's slots. + records: Box<[DecodeRecord]>, +} + +impl Av1Decoder { + pub(crate) fn create(context: VideoContext, config: &DecodeConfig) -> Result { + Ok(Self { + common: DecoderCommon::new(context, config.consumer_queue_family)?, + output_depth: config.output_depth, + parser: Av1Parser::new(), + active: None, + std_sequence: None, + slot_count: 0, + grain_applied: [false; MAX_DPB_SLOTS as usize], + shown_since_key: 0, + records: (0..DECODE_PIPELINE_DEPTH) + .map(|_| DecodeRecord::new(Av1Picture::default())) + .collect(), + }) + } + + /// The decode profile for a sequence header. + fn profile_for(seq: &SequenceHeader) -> Result<(DecodeProfile, ChromaFormat, BitDepth)> { + let cc = &seq.color_config; + if cc.mono_chrome { + return Err(PixelForgeError::InvalidInput( + "AV1 decode: monochrome streams are not supported".to_string(), + )); + } + let chroma_format = match (cc.subsampling_x, cc.subsampling_y) { + (true, true) => ChromaFormat::Yuv420, + (true, false) => ChromaFormat::Yuv422, + (false, false) => ChromaFormat::Yuv444, + (false, true) => { + return Err(PixelForgeError::InvalidInput( + "AV1 decode: invalid chroma subsampling".to_string(), + )); + } + }; + let bit_depth = match cc.bit_depth { + 8 => BitDepth::Eight, + 10 => BitDepth::Ten, + other => { + return Err(PixelForgeError::InvalidInput(format!( + "AV1 decode: unsupported bit depth {other}" + ))); + } + }; + // A session that may apply film grain has to say so in its profile. + let profile = DecodeProfile::av1( + seq.seq_profile as ash::vk::native::StdVideoAV1Profile, + seq.film_grain_params_present, + chroma_format, + bit_depth, + ); + Ok((profile, chroma_format, bit_depth)) + } + + /// Create (or recreate) the Vulkan session for the sequence header in + /// effect. + fn activate(&mut self) -> Result<()> { + let seq = self + .parser + .sequence_header() + .expect("a frame implies a sequence header") + .clone(); + if self.common.session.is_some() && self.active.as_ref() == Some(&seq) { + return Ok(()); + } + + let (profile, chroma_format, bit_depth) = Self::profile_for(&seq)?; + let needs = StreamNeeds { + codec: "AV1", + width: seq.max_frame_width(), + height: seq.max_frame_height(), + chroma_format, + bit_depth, + references: super::consts::NUM_REF_FRAMES as u32, + active_references: super::consts::REFS_PER_FRAME as u32, + slot_limit: MAX_DPB_SLOTS, + // Frames are shown in the order the stream gives them. + reorder_depth: 0, + output_depth: self.output_depth, + film_grain: seq.film_grain_params_present, + }; + let mut av1_caps = vk::VideoDecodeAV1CapabilitiesKHR::default(); + let plan = self + .common + .plan_session(profile.info(), &mut av1_caps, &needs)?; + if !plan.coincide { + // A frame shown again by `show_existing_frame` is read from its DPB + // slot, which only a coincident DPB hands out. + return Err(PixelForgeError::NoSuitableDevice( + "AV1 decode: needs a device whose DPB and output images coincide".to_string(), + )); + } + self.slot_count = plan.slot_count; + let std_seq = Box::new(StdSequenceHeader::new(&seq)); + self.common + .create_session(&plan, profile, |common, session| { + Self::build_session_params(common, session, &std_seq) + })?; + // The old session parameters are gone by now, so the header they + // pointed at can go too. + self.std_sequence = Some(std_seq); + self.active = Some(seq); + Ok(()) + } + + fn build_session_params( + common: &DecoderCommon, + session: vk::VideoSessionKHR, + std_seq: &StdSequenceHeader, + ) -> Result { + let mut av1_create = vk::VideoDecodeAV1SessionParametersCreateInfoKHR::default() + .std_sequence_header(std_seq.get()); + let create_info = vk::VideoSessionParametersCreateInfoKHR::default() + .video_session(session) + .push(&mut av1_create); + unsafe { + common + .video_queue_fn + .create_video_session_parameters(&create_info, None) + } + .map_err(PixelForgeError::vk("vkCreateVideoSessionParametersKHR")) + } + + /// Decode every frame of one temporal unit, returning the one it shows. + fn decode_temporal_unit(&mut self, tu: &[u8], pts: u64) -> Result { + let mut outcome = Decoded::Hidden; + let mut missing: Option = None; + for obu in iter_obus(tu) { + let obu = obu?; + // Frame data from before the first sequence header belongs to a + // stream joined mid-way. + let is_frame_data = matches!( + obu.header.obu_type, + ObuType::Frame + | ObuType::FrameHeader + | ObuType::RedundantFrameHeader + | ObuType::TileGroup + ); + if is_frame_data && self.parser.sequence_header().is_none() { + missing.get_or_insert_with(|| "no sequence header received yet".to_string()); + continue; + } + match self.parser.parse_obu(&obu)? { + None | Some(Av1Event::SequenceHeader) => {} + Some(Av1Event::MissingReferences(slots)) => { + missing.get_or_insert_with(|| { + format!("frame references empty slots (mask {slots:#04x})") + }); + } + Some(Av1Event::Frame(frame)) => { + // The frame shown by the previous one has already been + // handed over; close off its pipeline slot first. + if let Decoded::Shown(_) = outcome { + return Err(PixelForgeError::InvalidInput( + "AV1 decode: temporal unit shows more than one frame".to_string(), + )); + } + let result = self.decode_frame(tu, &frame, pts); + match result { + Ok(Some(picture)) => outcome = Decoded::Shown(picture), + Ok(None) => { + // A hidden frame: its work is complete as far as + // recording goes. + self.common.end_picture(Vec::new()); + } + Err(e) => { + self.parser.discard_frame(); + return Err(e); + } + } + } + } + } + match (outcome, missing) { + (Decoded::Hidden, Some(reason)) => Ok(Decoded::NeedsKeyframe(reason)), + (outcome, _) => Ok(outcome), + } + } + + /// Decode (or show) one frame and commit it to the reference state. + /// Returns the picture to output when the frame is shown. + fn decode_frame( + &mut self, + tu: &[u8], + frame: &Av1Frame, + pts: u64, + ) -> Result> { + let header = &frame.header; + self.activate()?; + + if header.show_existing_frame { + let shown = self.parser.refs().slots[header.frame_to_show_map_idx as usize].clone(); + let slot = shown.dpb_slot.ok_or_else(|| { + PixelForgeError::InvalidInput( + "AV1 decode: show_existing_frame of a slot with no picture".to_string(), + ) + })?; + let is_key = shown.frame_type == FrameType::Key; + if is_key { + self.shown_since_key = 0; + } + let mut picture = self.common.decoded_picture( + slot, + shown.upscaled_width, + shown.frame_height, + pts, + self.shown_since_key, + is_key, + ); + if self.grain_applied[slot as usize] { + self.common.use_grain_output(&mut picture); + } + self.shown_since_key += 1; + self.parser.commit_frame(None)?; + return Ok(Some(picture)); + } + + let slot = self.allocate_slot()?; + self.common.begin_decode_commands()?; + // Only the tile data goes to the driver, each tile at the offset the + // staging gives it. + let tile_data: Vec<&[u8]> = frame + .tiles() + .map(|t| &tu[t.offset..t.offset + t.size]) + .collect(); + let (buffer_range, offsets) = self.common.stage_slices(&tile_data, &[])?; + let grain = header.film_grain.apply_grain; + self.common.record_barriers(slot); + if grain { + self.common.record_grain_barrier(slot); + } + self.record_decode(frame, slot, buffer_range, &offsets, grain)?; + self.common.submit_decode()?; + self.grain_applied[slot as usize] = grain; + + let size = &header.frame_size; + let is_key = header.frame_type == FrameType::Key; + if is_key && header.show_frame { + self.shown_since_key = 0; + } + let mut picture = self.common.decoded_picture( + slot, + size.upscaled_width, + size.frame_height, + pts, + self.shown_since_key, + is_key && header.show_frame, + ); + if grain { + self.common.use_grain_output(&mut picture); + } + self.parser.commit_frame(Some(slot))?; + if !header.show_frame { + return Ok(None); + } + self.shown_since_key += 1; + Ok(Some(picture)) + } + + /// A DPB slot no reference frame holds and no caller's frame pins. + fn allocate_slot(&self) -> Result { + loop { + let pins = &self.common.session()?.slot_pins; + let refs = self.parser.refs(); + if let Some(slot) = + (0..self.slot_count as u8).find(|&s| !refs.dpb_slot_in_use(s) && !pins.is_pinned(s)) + { + return Ok(slot); + } + if !pins.any_pinned() { + return Err(PixelForgeError::InvalidInput( + "AV1 decode: no free DPB slot".to_string(), + )); + } + pins.wait_for_release(); + } + } + + /// Fill and link this pipeline slot's record, and record the coding + /// commands from it. + fn record_decode( + &mut self, + frame: &Av1Frame, + slot: u8, + buffer_range: u64, + offsets: &[u32], + grain: bool, + ) -> Result<()> { + let header = &frame.header; + let active = self.common.session.as_ref().expect("active"); + let refs = self.parser.refs(); + + let tiles: Vec<_> = frame.tiles().collect(); + let record = &mut self.records[self.common.pipeline.current_index()]; + record.reset(offsets); + + // Every picture the frame references, once each: several reference + // names can point at the same picture. + let mut pushed = [false; MAX_DPB_SLOTS as usize]; + let named: Vec = if header.frame_is_intra() { + Vec::new() + } else { + header.ref_frame_idx.iter().map(|&i| i as usize).collect() + }; + for r in named.iter().map(|&i| &refs.slots[i]).filter(|r| r.valid) { + let Some(dpb_slot) = r.dpb_slot else { continue }; + if std::mem::replace(&mut pushed[dpb_slot as usize], true) { + continue; + } + record.push_reference( + dpb_slot, + active.dpb_entry(dpb_slot).2, + Some(vk::Extent2D { + width: r.upscaled_width, + height: r.frame_height, + }), + std_reference_info(r), + ); + } + + // What the slot set up here will hold once this frame is a reference. + // SAFETY: plain C data, for which all-zero is valid. + let mut setup_std: ash::vk::native::StdVideoDecodeAV1ReferenceInfo = + unsafe { std::mem::zeroed() }; + setup_std + .flags + .set_disable_frame_end_update_cdf(header.disable_frame_end_update_cdf as u32); + setup_std + .flags + .set_segmentation_enabled(header.segmentation.segmentation_enabled as u32); + setup_std.frame_type = header.frame_type as u8; + setup_std.RefFrameSignBias = header.ref_frame_sign_bias_mask(); + setup_std.OrderHint = header.order_hint; + setup_std.SavedOrderHints = header.order_hints; + + let picture = record.picture_mut(); + picture.std = Some(StdPictureInfo::new(header)); + picture.reference_name_slot_indices = reference_name_slot_indices(header, &refs.slots); + // The buffer holds tile data alone, as ffmpeg hands it over, so there is + // no header in it to point at. + picture.frame_header_offset = 0; + picture.tile_sizes.clear(); + picture + .tile_sizes + .extend(tiles.iter().map(|t| t.size as u32)); + + let size = &header.frame_size; + record.link(&LinkParams { + extent: vk::Extent2D { + width: size.upscaled_width, + height: size.frame_height, + }, + session: active.session, + session_params: active.session_params, + bitstream: self.common.bitstream_buffer(), + bitstream_range: buffer_range, + setup_slot: slot, + setup_view: active.dpb_entry(slot).2, + // With grain, the output must not be the reference picture, which + // stays grain-free. + output_view: if grain { + Some(active.grain_view(slot)) + } else { + active.output_image.map(|(_, _, view)| view) + }, + setup_std, + }); + self.common.record_coding(record); + Ok(()) + } +} + +impl CodecDecoder for Av1Decoder { + type Unit<'a> = &'a [u8]; + + const NAME: &'static str = "AV1"; + + fn common(&self) -> &DecoderCommon { + &self.common + } + + fn common_mut(&mut self) -> &mut DecoderCommon { + &mut self.common + } + + fn complete_prefix(buffer: &[u8]) -> usize { + // An error here is input that is not low-overhead OBUs; decoding the + // buffer reports it. + temporal_unit_starts(buffer) + .ok() + .and_then(|starts| starts.last().copied()) + .unwrap_or(0) + } + + fn split_units<'a>(&mut self, data: &'a [u8]) -> Result<(Vec<&'a [u8]>, Option)> { + let mut starts = temporal_unit_starts(data)?; + // Data before the first temporal delimiter is a temporal unit too, + // when the caller strips delimiters (as some containers do). + if starts.first() != Some(&0) { + starts.insert(0, 0); + } + let units = starts + .iter() + .zip(starts.iter().skip(1).chain([&data.len()])) + .map(|(&start, &end)| &data[start..end]) + .filter(|unit| !unit.is_empty()) + .collect(); + Ok((units, None)) + } + + /// AV1 tracks which reference slots hold what itself and reports a frame + /// it cannot decode, so every temporal unit is offered. + fn is_keyframe(_unit: &&[u8]) -> bool { + true + } + + fn decode_unit(&mut self, unit: &&[u8], pts: u64) -> Result { + let outcome = self.decode_temporal_unit(unit, pts)?; + if let Decoded::NeedsKeyframe(reason) = &outcome { + debug!("AV1 decode: {reason}"); + } + Ok(outcome) + } + + fn reorder_depth(&self) -> usize { + 0 + } +} diff --git a/src/decoder/av1/std_info.rs b/src/decoder/av1/std_info.rs new file mode 100644 index 00000000..64523347 --- /dev/null +++ b/src/decoder/av1/std_info.rs @@ -0,0 +1,354 @@ +//! Conversion of parsed headers to the Vulkan Video Std structures for AV1. +//! +//! The Std structures point to each other. Each builder here owns every +//! structure it points to, boxed so the pointers stay valid when the builder +//! moves; the structure it hands out is valid for as long as the builder +//! lives. + +use ash::vk::native::*; + +use super::consts::{NUM_REF_FRAMES, REFS_PER_FRAME}; +use super::frame_header::FrameHeader; +use super::params::{FilmGrainParams, GlobalMotionParams}; +use super::refs::RefSlot; +use super::sequence::SequenceHeader; + +fn zeroed() -> T { + // SAFETY: only used for the plain-data Std structures, for which all + // zero bits are a valid value (null pointers, zero numbers and flags). + unsafe { std::mem::zeroed() } +} + +/// `StdVideoAV1SequenceHeader` and the structures it points to. +pub struct StdSequenceHeader { + header: StdVideoAV1SequenceHeader, + _color: Box, + _timing: Option>, +} + +// SAFETY: the raw pointers only point into the boxes this value owns. +unsafe impl Send for StdSequenceHeader {} + +impl StdSequenceHeader { + pub fn new(seq: &SequenceHeader) -> Self { + let cc = &seq.color_config; + let mut color: Box = Box::new(zeroed()); + color.flags.set_mono_chrome(cc.mono_chrome as u32); + color.flags.set_color_range(cc.color_range as u32); + color + .flags + .set_separate_uv_delta_q(cc.separate_uv_delta_q as u32); + color + .flags + .set_color_description_present_flag(cc.color_description_present_flag as u32); + color.BitDepth = cc.bit_depth; + color.subsampling_x = cc.subsampling_x as u8; + color.subsampling_y = cc.subsampling_y as u8; + color.color_primaries = cc.color_primaries as u32; + color.transfer_characteristics = cc.transfer_characteristics as u32; + color.matrix_coefficients = cc.matrix_coefficients as u32; + color.chroma_sample_position = cc.chroma_sample_position as u32; + + let timing = seq.timing_info.as_ref().map(|t| { + let mut timing: Box = Box::new(zeroed()); + timing + .flags + .set_equal_picture_interval(t.equal_picture_interval as u32); + timing.num_units_in_display_tick = t.num_units_in_display_tick; + timing.time_scale = t.time_scale; + timing.num_ticks_per_picture_minus_1 = t.num_ticks_per_picture_minus_1; + timing + }); + + let mut header: StdVideoAV1SequenceHeader = zeroed(); + let f = &mut header.flags; + f.set_still_picture(seq.still_picture as u32); + f.set_reduced_still_picture_header(seq.reduced_still_picture_header as u32); + f.set_use_128x128_superblock(seq.use_128x128_superblock as u32); + f.set_enable_filter_intra(seq.enable_filter_intra as u32); + f.set_enable_intra_edge_filter(seq.enable_intra_edge_filter as u32); + f.set_enable_interintra_compound(seq.enable_interintra_compound as u32); + f.set_enable_masked_compound(seq.enable_masked_compound as u32); + f.set_enable_warped_motion(seq.enable_warped_motion as u32); + f.set_enable_dual_filter(seq.enable_dual_filter as u32); + f.set_enable_order_hint(seq.enable_order_hint as u32); + f.set_enable_jnt_comp(seq.enable_jnt_comp as u32); + f.set_enable_ref_frame_mvs(seq.enable_ref_frame_mvs as u32); + f.set_frame_id_numbers_present_flag(seq.frame_id_numbers_present_flag as u32); + f.set_enable_superres(seq.enable_superres as u32); + f.set_enable_cdef(seq.enable_cdef as u32); + f.set_enable_restoration(seq.enable_restoration as u32); + f.set_film_grain_params_present(seq.film_grain_params_present as u32); + f.set_timing_info_present_flag(seq.timing_info.is_some() as u32); + f.set_initial_display_delay_present_flag(seq.initial_display_delay_present_flag as u32); + header.seq_profile = seq.seq_profile as u32; + header.frame_width_bits_minus_1 = seq.frame_width_bits_minus_1; + header.frame_height_bits_minus_1 = seq.frame_height_bits_minus_1; + header.max_frame_width_minus_1 = seq.max_frame_width_minus_1; + header.max_frame_height_minus_1 = seq.max_frame_height_minus_1; + header.delta_frame_id_length_minus_2 = seq.delta_frame_id_length_minus_2; + header.additional_frame_id_length_minus_1 = seq.additional_frame_id_length_minus_1; + header.order_hint_bits_minus_1 = seq.order_hint_bits_minus_1(); + header.seq_force_integer_mv = seq.seq_force_integer_mv; + header.seq_force_screen_content_tools = seq.seq_force_screen_content_tools; + header.pColorConfig = &*color; + header.pTimingInfo = timing.as_deref().map_or(std::ptr::null(), |t| t); + Self { + header, + _color: color, + _timing: timing, + } + } + + pub fn get(&self) -> &StdVideoAV1SequenceHeader { + &self.header + } +} + +fn std_global_motion(gm: &GlobalMotionParams) -> StdVideoAV1GlobalMotion { + StdVideoAV1GlobalMotion { + GmType: gm.gm_type, + gm_params: gm.gm_params, + } +} + +fn std_film_grain(g: &FilmGrainParams) -> StdVideoAV1FilmGrain { + let mut s: StdVideoAV1FilmGrain = zeroed(); + s.flags + .set_chroma_scaling_from_luma(g.chroma_scaling_from_luma as u32); + s.flags.set_overlap_flag(g.overlap_flag as u32); + s.flags + .set_clip_to_restricted_range(g.clip_to_restricted_range as u32); + s.flags.set_update_grain(g.update_grain as u32); + s.grain_scaling_minus_8 = g.grain_scaling_minus_8; + s.ar_coeff_lag = g.ar_coeff_lag; + s.ar_coeff_shift_minus_6 = g.ar_coeff_shift_minus_6; + s.grain_scale_shift = g.grain_scale_shift; + s.grain_seed = g.grain_seed; + s.film_grain_params_ref_idx = g.film_grain_params_ref_idx; + s.num_y_points = g.num_y_points; + s.point_y_value = g.point_y_value; + s.point_y_scaling = g.point_y_scaling; + s.num_cb_points = g.num_cb_points; + s.point_cb_value = g.point_cb_value; + s.point_cb_scaling = g.point_cb_scaling; + s.num_cr_points = g.num_cr_points; + s.point_cr_value = g.point_cr_value; + s.point_cr_scaling = g.point_cr_scaling; + // The Std header declares these int8_t although they hold 0..=255. + s.ar_coeffs_y_plus_128 = g.ar_coeffs_y_plus_128.map(|v| v as i8); + s.ar_coeffs_cb_plus_128 = g.ar_coeffs_cb_plus_128.map(|v| v as i8); + s.ar_coeffs_cr_plus_128 = g.ar_coeffs_cr_plus_128.map(|v| v as i8); + s.cb_mult = g.cb_mult; + s.cb_luma_mult = g.cb_luma_mult; + s.cb_offset = g.cb_offset; + s.cr_mult = g.cr_mult; + s.cr_luma_mult = g.cr_luma_mult; + s.cr_offset = g.cr_offset; + s +} + +/// The arrays a `StdVideoAV1TileInfo` points to. +struct TileArrays { + mi_col_starts: Box<[u16]>, + mi_row_starts: Box<[u16]>, + width_in_sbs_minus_1: Box<[u16]>, + height_in_sbs_minus_1: Box<[u16]>, +} + +/// `StdVideoDecodeAV1PictureInfo` and the structures it points to. +pub struct StdPictureInfo { + info: StdVideoDecodeAV1PictureInfo, + _tile_info: Box, + _tile_arrays: TileArrays, + _quantization: Box, + _segmentation: Box, + _loop_filter: Box, + _cdef: Box, + _loop_restoration: Box, + _global_motion: Box, + _film_grain: Box, +} + +impl StdPictureInfo { + /// Build the picture info for a frame (not a `show_existing_frame`, + /// which is not decoded). + pub fn new(h: &FrameHeader) -> Self { + let size = &h.frame_size; + let ti = &h.tile_info; + let tile_arrays = TileArrays { + // Vulkan takes TileCols / TileRows entries, without the final + // MiCols / MiRows the spec's arrays end with. + mi_col_starts: ti.mi_col_starts[..ti.tile_cols as usize].into(), + mi_row_starts: ti.mi_row_starts[..ti.tile_rows as usize].into(), + width_in_sbs_minus_1: ti.width_in_sbs_minus_1.clone().into(), + height_in_sbs_minus_1: ti.height_in_sbs_minus_1.clone().into(), + }; + let mut tile_info: Box = Box::new(zeroed()); + tile_info + .flags + .set_uniform_tile_spacing_flag(ti.uniform_tile_spacing_flag as u32); + tile_info.TileCols = ti.tile_cols; + tile_info.TileRows = ti.tile_rows; + tile_info.context_update_tile_id = ti.context_update_tile_id; + tile_info.tile_size_bytes_minus_1 = ti.tile_size_bytes_minus_1; + tile_info.pMiColStarts = tile_arrays.mi_col_starts.as_ptr(); + tile_info.pMiRowStarts = tile_arrays.mi_row_starts.as_ptr(); + tile_info.pWidthInSbsMinus1 = tile_arrays.width_in_sbs_minus_1.as_ptr(); + tile_info.pHeightInSbsMinus1 = tile_arrays.height_in_sbs_minus_1.as_ptr(); + + let q = &h.quantization; + let mut quantization: Box = Box::new(zeroed()); + quantization.flags.set_using_qmatrix(q.using_qmatrix as u32); + quantization.flags.set_diff_uv_delta(q.diff_uv_delta as u32); + quantization.base_q_idx = q.base_q_idx; + quantization.DeltaQYDc = q.delta_q_y_dc; + quantization.DeltaQUDc = q.delta_q_u_dc; + quantization.DeltaQUAc = q.delta_q_u_ac; + quantization.DeltaQVDc = q.delta_q_v_dc; + quantization.DeltaQVAc = q.delta_q_v_ac; + quantization.qm_y = q.qm_y; + quantization.qm_u = q.qm_u; + quantization.qm_v = q.qm_v; + + let segmentation = Box::new(StdVideoAV1Segmentation { + FeatureEnabled: h.segmentation.feature_enabled_masks(), + FeatureData: h.segmentation.feature_data, + }); + + let lf = &h.loop_filter; + let mut loop_filter: Box = Box::new(zeroed()); + loop_filter + .flags + .set_loop_filter_delta_enabled(lf.loop_filter_delta_enabled as u32); + loop_filter + .flags + .set_loop_filter_delta_update(lf.loop_filter_delta_update as u32); + loop_filter.loop_filter_level = lf.loop_filter_level; + loop_filter.loop_filter_sharpness = lf.loop_filter_sharpness; + loop_filter.update_ref_delta = lf.update_ref_delta; + loop_filter.loop_filter_ref_deltas = lf.loop_filter_ref_deltas; + loop_filter.update_mode_delta = lf.update_mode_delta; + loop_filter.loop_filter_mode_deltas = lf.loop_filter_mode_deltas; + + let c = &h.cdef; + let cdef = Box::new(StdVideoAV1CDEF { + cdef_damping_minus_3: c.cdef_damping_minus_3, + cdef_bits: c.cdef_bits, + cdef_y_pri_strength: c.cdef_y_pri_strength, + cdef_y_sec_strength: c.cdef_y_sec_strength, + cdef_uv_pri_strength: c.cdef_uv_pri_strength, + cdef_uv_sec_strength: c.cdef_uv_sec_strength, + }); + + let lr = &h.loop_restoration; + let loop_restoration = Box::new(StdVideoAV1LoopRestoration { + FrameRestorationType: lr.frame_restoration_type.map(|t| t as u32), + LoopRestorationSize: lr.size_log2_minus_5(), + }); + let global_motion = Box::new(std_global_motion(&h.global_motion)); + let film_grain = Box::new(std_film_grain(&h.film_grain)); + + let mut info: StdVideoDecodeAV1PictureInfo = zeroed(); + let f = &mut info.flags; + f.set_error_resilient_mode(h.error_resilient_mode as u32); + f.set_disable_cdf_update(h.disable_cdf_update as u32); + f.set_use_superres(size.use_superres as u32); + f.set_render_and_frame_size_different(size.render_and_frame_size_different as u32); + f.set_allow_screen_content_tools(h.allow_screen_content_tools as u32); + f.set_is_filter_switchable(h.is_filter_switchable as u32); + f.set_force_integer_mv(h.force_integer_mv as u32); + f.set_frame_size_override_flag(h.frame_size_override_flag as u32); + f.set_buffer_removal_time_present_flag(h.buffer_removal_time_present_flag as u32); + f.set_allow_intrabc(h.allow_intrabc as u32); + f.set_frame_refs_short_signaling(h.frame_refs_short_signaling as u32); + f.set_allow_high_precision_mv(h.allow_high_precision_mv as u32); + f.set_is_motion_mode_switchable(h.is_motion_mode_switchable as u32); + f.set_use_ref_frame_mvs(h.use_ref_frame_mvs as u32); + f.set_disable_frame_end_update_cdf(h.disable_frame_end_update_cdf as u32); + f.set_allow_warped_motion(h.allow_warped_motion as u32); + f.set_reduced_tx_set(h.reduced_tx_set as u32); + f.set_reference_select(h.reference_select as u32); + f.set_skip_mode_present(h.skip_mode_present as u32); + f.set_delta_q_present(h.delta.delta_q_present as u32); + f.set_delta_lf_present(h.delta.delta_lf_present as u32); + f.set_delta_lf_multi(h.delta.delta_lf_multi as u32); + f.set_segmentation_enabled(h.segmentation.segmentation_enabled as u32); + f.set_segmentation_update_map(h.segmentation.segmentation_update_map as u32); + f.set_segmentation_temporal_update(h.segmentation.segmentation_temporal_update as u32); + f.set_segmentation_update_data(h.segmentation.segmentation_update_data as u32); + f.set_UsesLr(lr.uses_lr as u32); + f.set_usesChromaLr(lr.uses_chroma_lr as u32); + f.set_apply_grain(h.film_grain.apply_grain as u32); + info.frame_type = h.frame_type as u32; + info.current_frame_id = h.current_frame_id; + info.OrderHint = h.order_hint; + info.primary_ref_frame = h.primary_ref_frame; + info.refresh_frame_flags = h.refresh_frame_flags; + info.interpolation_filter = h.interpolation_filter as u32; + info.TxMode = h.tx_mode as u32; + info.delta_q_res = h.delta.delta_q_res; + info.delta_lf_res = h.delta.delta_lf_res; + info.SkipModeFrame = h.skip_mode_frame; + info.coded_denom = size.coded_denom; + info.OrderHints = h.order_hints; + // Section 5.9.2 indexes expectedFrameId by i, for LAST_FRAME + i. + info.expectedFrameId[..REFS_PER_FRAME].copy_from_slice(&h.expected_frame_id); + info.pTileInfo = &*tile_info; + info.pQuantization = &*quantization; + info.pSegmentation = &*segmentation; + info.pLoopFilter = &*loop_filter; + info.pCDEF = &*cdef; + info.pLoopRestoration = &*loop_restoration; + info.pGlobalMotion = &*global_motion; + info.pFilmGrain = &*film_grain; + + Self { + info, + _tile_info: tile_info, + _tile_arrays: tile_arrays, + _quantization: quantization, + _segmentation: segmentation, + _loop_filter: loop_filter, + _cdef: cdef, + _loop_restoration: loop_restoration, + _global_motion: global_motion, + _film_grain: film_grain, + } + } + + pub fn get(&self) -> &StdVideoDecodeAV1PictureInfo { + &self.info + } +} + +/// `StdVideoDecodeAV1ReferenceInfo` for a reference slot (or, after the +/// update, for the frame just decoded). +pub fn std_reference_info(slot: &RefSlot) -> StdVideoDecodeAV1ReferenceInfo { + let mut info: StdVideoDecodeAV1ReferenceInfo = zeroed(); + info.flags + .set_disable_frame_end_update_cdf(slot.disable_frame_end_update_cdf as u32); + info.flags + .set_segmentation_enabled(slot.segmentation_enabled as u32); + info.frame_type = slot.frame_type as u8; + info.RefFrameSignBias = slot.ref_frame_sign_bias; + info.OrderHint = slot.order_hint; + info.SavedOrderHints = slot.saved_order_hints; + info +} + +/// `VkVideoDecodeAV1PictureInfoKHR::referenceNameSlotIndices`: the DPB slot +/// of each of LAST_FRAME..=ALTREF_FRAME, or -1. Intra frames use none. +pub fn reference_name_slot_indices( + header: &FrameHeader, + slots: &[RefSlot; NUM_REF_FRAMES], +) -> [i32; REFS_PER_FRAME] { + if header.frame_is_intra() { + return [-1; REFS_PER_FRAME]; + } + header.ref_frame_idx.map(|idx| { + slots[idx as usize] + .dpb_slot + .map_or(-1, |dpb_slot| dpb_slot as i32) + }) +} diff --git a/src/decoder/av1/stream_tests.rs b/src/decoder/av1/stream_tests.rs new file mode 100644 index 00000000..515f113f --- /dev/null +++ b/src/decoder/av1/stream_tests.rs @@ -0,0 +1,621 @@ +//! Parser tests against real encoder output. +//! +//! The fixtures in `tests/data/av1` are 320x240 (`aom_mono`: 176x144) +//! streams of 12 to 15 frames in the low-overhead OBU format, from libaom +//! (through ffmpeg's `libaom-av1` and `aomenc --obu`) and SVT-AV1. Each +//! `.expected` holds what ffmpeg's `trace_headers` bitstream filter +//! printed for that stream, reduced to one `F` line per frame header and one +//! `T` line per tile group, each a list of `syntax_element=value`. The +//! script that reduced them, `tests/data/av1/extract_trace.py`, also lists +//! the encoder settings; `header_bits` is the position after the header's last +//! syntax element minus that of its first. +//! +//! | stream | what it covers | +//! |-------------------|--------------------------------------------------------| +//! | `aom_altref` | hidden alt-refs, `show_existing_frame`, skip mode | +//! | `aom_tiles` | 2x2 uniform tiles, two-byte tile sizes | +//! | `aom_tilegroups` | frame header OBUs with two tile groups each, 128x128 | +//! | | superblocks, quantizer matrices, segmentation, delta | +//! | | lf, loop restoration, decoder model and removal times | +//! | `aom_10bit` | 10-bit Main profile | +//! | `aom_grain` | film grain (libaom denoiser) | +//! | `svt_grain` | SVT-AV1 with film grain, `update_grain = 0` reuse | +//! | `aom_errres` | error resilient frames, frame ids, `ref_order_hint`, | +//! | | S-frames | +//! | `aom_superres` | superres | +//! | `aom_resize` | frame size override, `frame_size_with_refs`, render | +//! | | size | +//! | `aom_mono` | monochrome, loop restoration | +//! | `aom_annexb` | Annex B framing, which must be rejected | +//! +//! No encoder at hand produces `frame_refs_short_signaling`, non-uniform +//! tile spacing or redundant frame headers; `refs.rs` and `tile.rs` test +//! those paths on synthetic input. + +use super::frame_header::FrameHeader; +use super::obu::{ObuType, iter_obus, temporal_unit_starts}; +use super::parser::{Av1Event, Av1Frame, Av1Parser}; +use super::sequence::SequenceHeader; +use super::std_info::{ + StdPictureInfo, StdSequenceHeader, reference_name_slot_indices, std_reference_info, +}; + +macro_rules! fixture { + ($name:literal) => { + ( + $name, + include_bytes!(concat!("../../../tests/data/av1/", $name, ".obu")).as_slice(), + include_str!(concat!("../../../tests/data/av1/", $name, ".expected")), + ) + }; +} + +const FIXTURES: [(&str, &[u8], &str); 10] = [ + fixture!("aom_altref"), + fixture!("aom_tiles"), + fixture!("aom_tilegroups"), + fixture!("aom_10bit"), + fixture!("aom_grain"), + fixture!("svt_grain"), + fixture!("aom_errres"), + fixture!("aom_superres"), + fixture!("aom_resize"), + fixture!("aom_mono"), +]; + +const ANNEX_B: &[u8] = include_bytes!("../../../tests/data/av1/aom_annexb.obu"); + +fn fixture(name: &str) -> &'static [u8] { + FIXTURES.iter().find(|f| f.0 == name).unwrap().1 +} + +/// Everything parsing a whole stream produced. +struct Parsed { + sequence: SequenceHeader, + frames: Vec, + /// `(payload_offset, end)` of every tile group carrying OBU. + tile_group_obus: Vec<(usize, usize)>, + /// Frames shown, by `show_frame` or `show_existing_frame`. + shown: usize, +} + +/// Parse a stream, committing every frame, and check what holds for every +/// stream: every OBU parses and every frame is complete. +fn parse_stream(name: &str, data: &[u8]) -> Parsed { + let mut parser = Av1Parser::new(); + let mut frames = Vec::new(); + let mut tile_group_obus = Vec::new(); + let mut shown = 0; + let mut next_dpb_slot = 0u8; + for obu in iter_obus(data) { + let obu = obu.unwrap_or_else(|e| panic!("{name}: OBU framing: {e}")); + if matches!(obu.header.obu_type, ObuType::TileGroup | ObuType::Frame) { + tile_group_obus.push(( + obu.payload_offset(), + obu.payload_offset() + obu.payload.len(), + )); + } + let event = parser + .parse_obu(&obu) + .unwrap_or_else(|e| panic!("{name}: OBU at byte {}: {e}", obu.offset)); + match event { + Some(Av1Event::Frame(frame)) => { + let h = &frame.header; + if h.show_frame || h.show_existing_frame { + shown += 1; + } + if !h.show_existing_frame { + check_vulkan_conversion(&parser, h); + } + // A stand-in for the DPB slot the decoder would pick. + parser.commit_frame(Some(next_dpb_slot)).unwrap(); + next_dpb_slot = (next_dpb_slot + 1) % 16; + frames.push(*frame); + } + Some(Av1Event::MissingReferences(slots)) => { + panic!("{name}: missing references {slots:#010b}") + } + Some(Av1Event::SequenceHeader) | None => {} + } + } + Parsed { + sequence: parser + .sequence_header() + .expect("stream has a sequence header") + .clone(), + frames, + tile_group_obus, + shown, + } +} + +/// Build the Vulkan structures for a frame about to be decoded, as the +/// decoder will, and spot-check that they carry the parsed values. +fn check_vulkan_conversion(parser: &Av1Parser, h: &FrameHeader) { + let seq = StdSequenceHeader::new(parser.sequence_header().unwrap()); + let std_seq = seq.get(); + // SAFETY: the pointer targets are owned by `seq`, which is alive. + let color = unsafe { &*std_seq.pColorConfig }; + assert_eq!( + color.BitDepth, + parser.sequence_header().unwrap().color_config.bit_depth + ); + + let pic = StdPictureInfo::new(h); + let info = pic.get(); + assert_eq!(info.OrderHint, h.order_hint); + assert_eq!(info.refresh_frame_flags, h.refresh_frame_flags); + // SAFETY: as above, owned by `pic`. + let tile_info = unsafe { &*info.pTileInfo }; + let quant = unsafe { &*info.pQuantization }; + assert_eq!(quant.base_q_idx, h.quantization.base_q_idx); + assert_eq!(tile_info.TileCols, h.tile_info.tile_cols); + let cols = + unsafe { std::slice::from_raw_parts(tile_info.pMiColStarts, tile_info.TileCols as usize) }; + assert_eq!(cols, &h.tile_info.mi_col_starts[..cols.len()]); + + let indices = reference_name_slot_indices(h, &parser.refs().slots); + if !h.frame_is_intra() { + for (i, &idx) in h.ref_frame_idx.iter().enumerate() { + let slot = &parser.refs().slots[idx as usize]; + assert!(slot.valid); + assert_eq!(indices[i], slot.dpb_slot.unwrap() as i32); + assert_eq!(std_reference_info(slot).OrderHint, h.order_hints[i + 1]); + } + } else { + assert!(indices.iter().all(|&i| i == -1)); + } +} + +/// The `syntax_element=value` pairs of one line. +type Elements = Vec<(String, i64)>; + +/// The `F` (frame header) and `T` (tile group) lines of an `.expected` file. +fn parse_expected(text: &str) -> (Vec, Vec) { + let mut frames = Vec::new(); + let mut tile_groups = Vec::new(); + for line in text.lines() { + let mut words = line.split_whitespace(); + let kind = words.next().unwrap(); + let pairs: Elements = words + .map(|w| { + let (k, v) = w.split_once('=').unwrap(); + (k.to_string(), v.parse().unwrap()) + }) + .collect(); + match kind { + "F" => frames.push(pairs), + "T" => tile_groups.push(pairs), + other => panic!("unknown line kind {other}"), + } + } + (frames, tile_groups) +} + +/// The parsed value of the syntax element `trace_headers` calls `key`. +/// `return_coded` is the traced value, returned where the parser keeps a +/// derived value that cannot be compared with it. +fn value_of(f: &Av1Frame, key: &str, return_coded: i64) -> i64 { + let h = &f.header; + let (name, index) = match key.split_once('[') { + Some((name, rest)) => ( + name, + Some(rest.trim_end_matches(']').parse::().unwrap()), + ), + None => (key, None), + }; + let b = |v: bool| v as i64; + let lr = &h.loop_restoration; + match (name, index) { + ("obu", None) => match f.header_obu_type { + ObuType::FrameHeader => 3, + ObuType::Frame => 6, + ObuType::RedundantFrameHeader => 7, + other => panic!("header in {other:?}"), + }, + ("show_existing_frame", None) => b(h.show_existing_frame), + ("frame_to_show_map_idx", None) => h.frame_to_show_map_idx as i64, + ("frame_type", None) => h.frame_type as i64, + ("show_frame", None) => b(h.show_frame), + ("showable_frame", None) => b(h.showable_frame), + ("error_resilient_mode", None) => b(h.error_resilient_mode), + ("disable_cdf_update", None) => b(h.disable_cdf_update), + ("allow_screen_content_tools", None) => b(h.allow_screen_content_tools), + // Intra frames code force_integer_mv but always use 1, which is the + // value kept; only an inter frame's coded value is comparable. + ("force_integer_mv", None) if h.frame_is_intra() => return_coded, + ("force_integer_mv", None) => b(h.force_integer_mv), + ("current_frame_id", None) => h.current_frame_id as i64, + ("frame_size_override_flag", None) => b(h.frame_size_override_flag), + ("order_hint", None) => h.order_hint as i64, + ("primary_ref_frame", None) => h.primary_ref_frame as i64, + ("refresh_frame_flags", None) => h.refresh_frame_flags as i64, + ("ref_frame_idx", Some(i)) => h.ref_frame_idx[i] as i64, + ("frame_width_minus_1", None) => h.frame_size.upscaled_width as i64 - 1, + ("frame_height_minus_1", None) => h.frame_size.frame_height as i64 - 1, + ("render_width_minus_1", None) => h.frame_size.render_width as i64 - 1, + ("render_height_minus_1", None) => h.frame_size.render_height as i64 - 1, + ("use_superres", None) => b(h.frame_size.use_superres), + ("coded_denom", None) => h.frame_size.coded_denom as i64, + ("allow_intrabc", None) => b(h.allow_intrabc), + ("allow_high_precision_mv", None) => b(h.allow_high_precision_mv), + ("is_filter_switchable", None) => b(h.is_filter_switchable), + ("interpolation_filter", None) => h.interpolation_filter as i64, + ("use_ref_frame_mvs", None) => b(h.use_ref_frame_mvs), + ("disable_frame_end_update_cdf", None) => b(h.disable_frame_end_update_cdf), + ("tile_cols_log2", None) => h.tile_info.tile_cols_log2 as i64, + ("tile_rows_log2", None) => h.tile_info.tile_rows_log2 as i64, + ("context_update_tile_id", None) => h.tile_info.context_update_tile_id as i64, + ("tile_size_bytes_minus1", None) => h.tile_info.tile_size_bytes_minus_1 as i64, + ("base_q_idx", None) => h.quantization.base_q_idx as i64, + ("using_qmatrix", None) => b(h.quantization.using_qmatrix), + ("qm_y", None) => h.quantization.qm_y as i64, + ("qm_u", None) => h.quantization.qm_u as i64, + ("qm_v", None) => h.quantization.qm_v as i64, + ("segmentation_enabled", None) => b(h.segmentation.segmentation_enabled), + ("segmentation_update_map", None) => b(h.segmentation.segmentation_update_map), + ("segmentation_temporal_update", None) => b(h.segmentation.segmentation_temporal_update), + ("segmentation_update_data", None) => b(h.segmentation.segmentation_update_data), + ("delta_q_present", None) => b(h.delta.delta_q_present), + ("delta_q_res", None) => h.delta.delta_q_res as i64, + ("delta_lf_present", None) => b(h.delta.delta_lf_present), + ("delta_lf_res", None) => h.delta.delta_lf_res as i64, + ("delta_lf_multi", None) => b(h.delta.delta_lf_multi), + ("loop_filter_level", Some(i)) => h.loop_filter.loop_filter_level[i] as i64, + ("loop_filter_sharpness", None) => h.loop_filter.loop_filter_sharpness as i64, + ("loop_filter_delta_enabled", None) => b(h.loop_filter.loop_filter_delta_enabled), + ("loop_filter_delta_update", None) => b(h.loop_filter.loop_filter_delta_update), + ("cdef_damping_minus_3", None) => h.cdef.cdef_damping_minus_3 as i64, + ("cdef_bits", None) => h.cdef.cdef_bits as i64, + ("cdef_y_pri_strength", Some(i)) => h.cdef.cdef_y_pri_strength[i] as i64, + ("cdef_y_sec_strength", Some(i)) => h.cdef.cdef_y_sec_strength[i] as i64, + // trace_headers prints the coded lr_type, before the remapping. + ("lr_type", Some(i)) => [0, 2, 3, 1][lr.frame_restoration_type[i] as usize], + // cbs_av1 reads lr_unit_shift as one increment and prints the sum. + ("lr_unit_shift", None) => lr.lr_unit_shift as i64, + ("lr_uv_shift", None) => lr.lr_uv_shift as i64, + ("tx_mode", None) => h.tx_mode as i64, + ("reference_select", None) => b(h.reference_select), + ("skip_mode_present", None) => b(h.skip_mode_present), + ("allow_warped_motion", None) => b(h.allow_warped_motion), + ("reduced_tx_set", None) => b(h.reduced_tx_set), + ("apply_grain", None) => b(h.film_grain.apply_grain), + ("grain_seed", None) => h.film_grain.grain_seed as i64, + ("update_grain", None) => b(h.film_grain.update_grain), + ("film_grain_params_ref_idx", None) => h.film_grain.film_grain_params_ref_idx as i64, + ("num_y_points", None) => h.film_grain.num_y_points as i64, + ("num_cb_points", None) => h.film_grain.num_cb_points as i64, + ("num_cr_points", None) => h.film_grain.num_cr_points as i64, + ("header_bits", None) => h.header_bits as i64, + _ => panic!("no mapping for trace element {key}"), + } +} + +/// Every syntax element `trace_headers` printed matches the parse, frame by +/// frame and tile group by tile group, including each header's bit length. +#[test] +fn headers_match_trace_headers() { + for (name, data, expected) in FIXTURES { + let parsed = parse_stream(name, data); + let (frames, tile_groups) = parse_expected(expected); + assert_eq!(parsed.frames.len(), frames.len(), "{name}: frame count"); + for (n, (frame, pairs)) in parsed.frames.iter().zip(&frames).enumerate() { + for (key, want) in pairs { + let got = value_of(frame, key, *want); + assert_eq!(got, *want, "{name}: frame {n}: {key}"); + } + // skip_mode_present is coded exactly when skip mode is allowed, + // so its presence checks the SkipModeFrame derivation. + let skip_coded = pairs.iter().any(|(k, _)| k == "skip_mode_present"); + let h = &frame.header; + if !h.show_existing_frame { + assert_eq!( + h.skip_mode_frame[0] != 0, + skip_coded, + "{name}: frame {n}: skipModeAllowed" + ); + } + } + let parsed_groups: Vec<_> = parsed.frames.iter().flat_map(|f| &f.tile_groups).collect(); + assert_eq!( + parsed_groups.len(), + tile_groups.len(), + "{name}: tile groups" + ); + for (n, (tg, pairs)) in parsed_groups.iter().zip(&tile_groups).enumerate() { + for (key, want) in pairs { + let got = match key.as_str() { + "obu" => continue, + "tile_start_and_end_present_flag" => tg.tile_start_and_end_present_flag as i64, + "tg_start" => tg.tg_start as i64, + "tg_end" => tg.tg_end as i64, + other => panic!("no mapping for {other}"), + }; + assert_eq!(got, *want, "{name}: tile group {n}: {key}"); + } + } + } +} + +/// Tiles cover every tile group exactly: each tile starts right after the +/// previous one and its size field, and the last ends at the end of the OBU. +/// The frame header offset points at the OBU that carried the header. +#[test] +fn tiles_and_header_offsets_cover_the_obus() { + for (name, data, _) in FIXTURES { + let parsed = parse_stream(name, data); + let mut obus = parsed.tile_group_obus.iter(); + for (n, frame) in parsed.frames.iter().enumerate() { + let h = &frame.header; + let first = data[frame.frame_header_offset]; + let obu_type = (first >> 3) & 0x0f; + assert!( + matches!(obu_type, 3 | 6), + "{name}: frame {n}: header OBU type" + ); + assert_eq!( + frame.frame_header_bytes, + h.header_bits.div_ceil(8) + + (frame.header_obu_type == ObuType::FrameHeader && h.header_bits % 8 == 0) + as usize, + "{name}: frame {n}: header bytes vs bits" + ); + if h.show_existing_frame { + assert!(frame.tile_groups.is_empty()); + continue; + } + assert_eq!( + frame.tiles().count(), + h.tile_info.num_tiles(), + "{name}: frame {n}" + ); + let size_bytes = h.tile_info.tile_size_bytes() as usize; + for tg in &frame.tile_groups { + let &(_, obu_end) = obus.next().expect("a tile group OBU per tile group"); + // Every tile but the last is preceded by its size field. + for (k, pair) in tg.tiles.windows(2).enumerate() { + let field = if k + 2 < tg.tiles.len() { + size_bytes + } else { + 0 + }; + assert_eq!(pair[0].offset + pair[0].size + field, pair[1].offset); + } + let last = tg.tiles.last().unwrap(); + assert_eq!( + last.offset + last.size, + obu_end, + "{name}: frame {n}: last tile" + ); + assert!(tg.tiles.iter().all(|t| t.size > 0)); + } + } + assert!(obus.next().is_none(), "{name}: tile group OBUs left over"); + } +} + +/// Each temporal unit shows exactly one frame, and the temporal unit +/// boundaries split the stream into units that parse on their own. +#[test] +fn temporal_units_show_one_frame_each() { + for (name, data, _) in FIXTURES { + let parsed = parse_stream(name, data); + let starts = temporal_unit_starts(data).unwrap(); + assert_eq!( + starts[0], 0, + "{name}: stream starts with a temporal delimiter" + ); + assert_eq!( + starts.len(), + parsed.shown, + "{name}: one shown frame per temporal unit" + ); + + // Feeding the units one at a time gives the same frames. + let mut parser = Av1Parser::new(); + let mut count = 0; + let mut bounds = starts.clone(); + bounds.push(data.len()); + for w in bounds.windows(2) { + for obu in iter_obus(&data[w[0]..w[1]]) { + if let Some(Av1Event::Frame(_)) = parser.parse_obu(&obu.unwrap()).unwrap() { + parser.commit_frame(None).unwrap(); + count += 1; + } + } + } + assert_eq!(count, parsed.frames.len(), "{name}: frames per unit"); + + // A byte stream cut mid-OBU still yields the complete units. + let cut = &data[..data.len() - 3]; + assert_eq!(temporal_unit_starts(cut).unwrap(), starts, "{name}"); + } +} + +/// The features each fixture exists for are really in it. +#[test] +fn fixtures_cover_their_features() { + let altref = parse_stream("aom_altref", fixture("aom_altref")); + assert!( + altref + .frames + .iter() + .any(|f| !f.header.show_frame && !f.header.show_existing_frame) + ); + assert!(altref.frames.iter().any(|f| f.header.show_existing_frame)); + assert_eq!(altref.shown, 15); + + let tiles = parse_stream("aom_tiles", fixture("aom_tiles")); + assert!(tiles.frames.iter().all(|f| f.header.show_existing_frame + || (f.header.tile_info.tile_cols, f.header.tile_info.tile_rows) == (2, 2))); + + let groups = parse_stream("aom_tilegroups", fixture("aom_tilegroups")); + assert!(groups.sequence.use_128x128_superblock); + assert!(groups.sequence.decoder_model_info.is_some()); + assert!(groups.frames.iter().all(|f| f.tile_groups.len() == 2)); + assert!( + groups + .frames + .iter() + .any(|f| f.header.segmentation.segmentation_enabled) + ); + assert!( + groups + .frames + .iter() + .any(|f| f.header.quantization.using_qmatrix) + ); + assert!( + groups + .frames + .iter() + .any(|f| f.header.loop_restoration.uses_lr) + ); + + let ten = parse_stream("aom_10bit", fixture("aom_10bit")); + assert_eq!(ten.sequence.seq_profile, 0); + assert_eq!(ten.sequence.color_config.bit_depth, 10); + + for name in ["aom_grain", "svt_grain"] { + let grain = parse_stream(name, fixture(name)); + assert!(grain.sequence.film_grain_params_present); + assert!(grain.frames.iter().any(|f| f.header.film_grain.apply_grain)); + } + let errres = parse_stream("aom_errres", fixture("aom_errres")); + assert!(errres.sequence.frame_id_numbers_present_flag); + assert!( + errres + .frames + .iter() + .any(|f| f.header.frame_type == super::frame_header::FrameType::Switch) + ); + assert!( + errres + .frames + .iter() + .filter(|f| !f.header.frame_is_intra()) + .all(|f| f.header.error_resilient_mode) + ); + + let superres = parse_stream("aom_superres", fixture("aom_superres")); + assert!( + superres + .frames + .iter() + .any(|f| f.header.frame_size.use_superres + && f.header.frame_size.frame_width < f.header.frame_size.upscaled_width) + ); + + let resize = parse_stream("aom_resize", fixture("aom_resize")); + assert!( + resize + .frames + .iter() + .any(|f| f.header.frame_size.frame_width != 320) + ); + assert!( + resize + .frames + .iter() + .all(|f| f.header.frame_size.render_width == 320) + ); + assert!( + resize + .frames + .iter() + .any(|f| f.header.frame_size.found_ref.is_some()) + ); + + let mono = parse_stream("aom_mono", fixture("aom_mono")); + assert!(mono.sequence.color_config.mono_chrome); +} + +/// Annex B input is recognised and rejected, not misparsed. +#[test] +fn rejects_annex_b() { + let err = iter_obus(ANNEX_B).next().unwrap().unwrap_err(); + assert!(err.to_string().contains("Annex B"), "{err}"); + assert!(temporal_unit_starts(ANNEX_B).is_err()); +} + +/// Starting after the key frame, or losing a frame, reports missing +/// references instead of failing, and the next key frame recovers. +#[test] +fn missing_references_wait_for_a_key_frame() { + let data = fixture("aom_altref"); + let starts = temporal_unit_starts(data).unwrap(); + + // Skip the first temporal unit's frames, keep its sequence header. + let mut parser = Av1Parser::new(); + let mut missing = 0; + let mut frames = 0; + for obu in iter_obus(data) { + let obu = obu.unwrap(); + if obu.offset < starts[1] && obu.header.obu_type != ObuType::SequenceHeader { + continue; + } + match parser.parse_obu(&obu).unwrap() { + Some(Av1Event::MissingReferences(slots)) => { + assert_ne!(slots, 0); + missing += 1; + } + Some(Av1Event::Frame(_)) => { + parser.commit_frame(None).unwrap(); + frames += 1; + } + _ => {} + } + } + assert!(missing > 0, "frames after the lost key frame are flagged"); + assert_eq!( + frames, 0, + "and none of them decode (there is one key frame)" + ); + + // Discarding a frame leaves the references it would have refreshed + // empty; the stream continues once the slots are reset by a key frame. + let mut parser = Av1Parser::new(); + let mut seen = 0; + let mut flagged = false; + for obu in iter_obus(data) { + match parser.parse_obu(&obu.unwrap()).unwrap() { + Some(Av1Event::Frame(_)) => { + seen += 1; + if seen == 2 { + parser.discard_frame(); + } else { + parser.commit_frame(None).unwrap(); + } + } + Some(Av1Event::MissingReferences(_)) => flagged = true, + _ => {} + } + } + assert!(flagged, "a frame needing the discarded one is flagged"); +} + +/// Corrupt input never panics: flip bits throughout every stream and parse +/// everything, ignoring the errors. +#[test] +fn corrupt_streams_do_not_panic() { + for (_, data, _) in FIXTURES { + for step in [7usize, 13, 29, 61] { + let mut corrupt = data.to_vec(); + let mut i = step; + while i < corrupt.len() { + corrupt[i] ^= 1 << (i % 8); + i += step * 17; + } + let mut parser = Av1Parser::new(); + for obu in iter_obus(&corrupt) { + let Ok(obu) = obu else { break }; + if let Ok(Some(Av1Event::Frame(_))) = parser.parse_obu(&obu) { + parser.commit_frame(None).unwrap(); + } + } + for len in (0..corrupt.len()).step_by(step * 101) { + let _ = temporal_unit_starts(&corrupt[..len]); + } + } + } +} diff --git a/src/decoder/av1/tile.rs b/src/decoder/av1/tile.rs new file mode 100644 index 00000000..ede4d87a --- /dev/null +++ b/src/decoder/av1/tile.rs @@ -0,0 +1,335 @@ +//! Tile layout and tile group OBUs (AV1 specification sections 5.9.15 and +//! 5.11.1). + +use super::bits::BitReader; +use super::consts::{MAX_TILE_AREA, MAX_TILE_COLS, MAX_TILE_ROWS, MAX_TILE_WIDTH}; +use crate::error::{PixelForgeError, Result}; + +/// `tile_info()`, with the derived tile grid. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TileInfo { + pub uniform_tile_spacing_flag: bool, + pub tile_cols_log2: u8, + pub tile_rows_log2: u8, + pub tile_cols: u8, + pub tile_rows: u8, + /// `MiColStarts`, `tile_cols + 1` entries (the last is `MiCols`). + pub mi_col_starts: Vec, + /// `MiRowStarts`, `tile_rows + 1` entries (the last is `MiRows`). + pub mi_row_starts: Vec, + /// `width_in_sbs_minus_1` per tile column. Coded for non-uniform + /// spacing; derived from the grid for uniform spacing, which is what + /// Vulkan expects either way. + pub width_in_sbs_minus_1: Vec, + /// `height_in_sbs_minus_1` per tile row, like `width_in_sbs_minus_1`. + pub height_in_sbs_minus_1: Vec, + pub context_update_tile_id: u16, + /// `TileSizeBytes - 1`; 3 (four-byte sizes) when not coded, which only + /// happens with a single tile, where no size is ever read. + pub tile_size_bytes_minus_1: u8, +} + +impl TileInfo { + /// `NumTiles`. + pub fn num_tiles(&self) -> usize { + self.tile_cols as usize * self.tile_rows as usize + } + + /// `TileSizeBytes`. + pub fn tile_size_bytes(&self) -> u32 { + self.tile_size_bytes_minus_1 as u32 + 1 + } +} + +/// `tile_log2(blkSize, target)`. +fn tile_log2(blk_size: u32, target: u32) -> u32 { + let mut k = 0; + while (blk_size << k) < target { + k += 1; + } + k +} + +/// Superblock sizes per tile from the superblock-unit starts. +fn sizes_minus_1(starts_sb: &[u32]) -> Vec { + starts_sb + .windows(2) + .map(|w| (w[1] - w[0] - 1) as u16) + .collect() +} + +/// Parse `tile_info()` for a frame of `mi_cols` by `mi_rows` mode-info units. +pub(crate) fn parse_tile_info( + r: &mut BitReader, + use_128x128_superblock: bool, + mi_cols: u32, + mi_rows: u32, +) -> Result { + if mi_cols == 0 || mi_rows == 0 { + return Err(PixelForgeError::InvalidInput( + "AV1: frame has zero size".to_string(), + )); + } + let (sb_cols, sb_rows, sb_shift) = if use_128x128_superblock { + ((mi_cols + 31) >> 5, (mi_rows + 31) >> 5, 5) + } else { + ((mi_cols + 15) >> 4, (mi_rows + 15) >> 4, 4) + }; + let sb_size = sb_shift + 2; + let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size; + let mut max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size); + let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols); + let max_log2_tile_cols = tile_log2(1, sb_cols.min(MAX_TILE_COLS)); + let max_log2_tile_rows = tile_log2(1, sb_rows.min(MAX_TILE_ROWS)); + let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols)); + + let mut info = TileInfo { + uniform_tile_spacing_flag: r.flag()?, + tile_size_bytes_minus_1: 3, + ..Default::default() + }; + // Tile starts in superblocks, ending with the frame size in superblocks. + let mut col_starts_sb = Vec::new(); + let mut row_starts_sb = Vec::new(); + let (tile_cols_log2, tile_rows_log2); + if info.uniform_tile_spacing_flag { + let mut cols_log2 = min_log2_tile_cols; + while cols_log2 < max_log2_tile_cols { + if !r.flag()? { + break; + } + cols_log2 += 1; + } + let tile_width_sb = (sb_cols + (1 << cols_log2) - 1) >> cols_log2; + col_starts_sb.extend((0..sb_cols).step_by(tile_width_sb as usize)); + + let min_log2_tile_rows = min_log2_tiles.saturating_sub(cols_log2); + let mut rows_log2 = min_log2_tile_rows; + while rows_log2 < max_log2_tile_rows { + if !r.flag()? { + break; + } + rows_log2 += 1; + } + let tile_height_sb = (sb_rows + (1 << rows_log2) - 1) >> rows_log2; + row_starts_sb.extend((0..sb_rows).step_by(tile_height_sb as usize)); + tile_cols_log2 = cols_log2; + tile_rows_log2 = rows_log2; + } else { + let mut widest_tile_sb = 0; + let mut start_sb = 0; + while start_sb < sb_cols { + if col_starts_sb.len() as u32 >= MAX_TILE_COLS { + return Err(PixelForgeError::InvalidInput( + "AV1: more than 64 tile columns".to_string(), + )); + } + col_starts_sb.push(start_sb); + let max_width = (sb_cols - start_sb).min(max_tile_width_sb); + let size_sb = r.ns(max_width)? + 1; + widest_tile_sb = widest_tile_sb.max(size_sb); + start_sb += size_sb; + } + tile_cols_log2 = tile_log2(1, col_starts_sb.len() as u32); + + if min_log2_tiles > 0 { + max_tile_area_sb = (sb_rows * sb_cols) >> (min_log2_tiles + 1); + } else { + max_tile_area_sb = sb_rows * sb_cols; + } + let max_tile_height_sb = (max_tile_area_sb / widest_tile_sb).max(1); + let mut start_sb = 0; + while start_sb < sb_rows { + if row_starts_sb.len() as u32 >= MAX_TILE_ROWS { + return Err(PixelForgeError::InvalidInput( + "AV1: more than 64 tile rows".to_string(), + )); + } + row_starts_sb.push(start_sb); + let max_height = (sb_rows - start_sb).min(max_tile_height_sb); + start_sb += r.ns(max_height)? + 1; + } + tile_rows_log2 = tile_log2(1, row_starts_sb.len() as u32); + } + + if col_starts_sb.len() as u32 > MAX_TILE_COLS || row_starts_sb.len() as u32 > MAX_TILE_ROWS { + return Err(PixelForgeError::InvalidInput( + "AV1: tile grid exceeds 64 columns or rows".to_string(), + )); + } + info.tile_cols = col_starts_sb.len() as u8; + info.tile_rows = row_starts_sb.len() as u8; + info.tile_cols_log2 = tile_cols_log2 as u8; + info.tile_rows_log2 = tile_rows_log2 as u8; + info.mi_col_starts = col_starts_sb + .iter() + .map(|&s| (s << sb_shift) as u16) + .chain([mi_cols as u16]) + .collect(); + info.mi_row_starts = row_starts_sb + .iter() + .map(|&s| (s << sb_shift) as u16) + .chain([mi_rows as u16]) + .collect(); + col_starts_sb.push(sb_cols); + row_starts_sb.push(sb_rows); + info.width_in_sbs_minus_1 = sizes_minus_1(&col_starts_sb); + info.height_in_sbs_minus_1 = sizes_minus_1(&row_starts_sb); + + if tile_cols_log2 > 0 || tile_rows_log2 > 0 { + info.context_update_tile_id = r.f(tile_cols_log2 + tile_rows_log2)? as u16; + if info.context_update_tile_id as usize >= info.num_tiles() { + return Err(PixelForgeError::InvalidInput( + "AV1: context_update_tile_id out of range".to_string(), + )); + } + info.tile_size_bytes_minus_1 = r.f(2)? as u8; + } + Ok(info) +} + +/// Where one tile's data sits. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TileLocation { + /// Byte offset of the first byte of tile data (after its + /// `tile_size_minus_1` field, if any), relative to the start of the + /// buffer the OBU iterator was given. + pub offset: usize, + /// Size of the tile data in bytes. + pub size: usize, +} + +/// One parsed tile group: the range of tiles it covers and where each one is. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TileGroup { + pub tile_start_and_end_present_flag: bool, + pub tg_start: u16, + pub tg_end: u16, + pub tiles: Vec, +} + +/// Parse `tile_group_obu(sz)` from `data`, which starts where the tile group +/// does: at the payload of a tile group OBU, or after the byte-aligned frame +/// header in a frame OBU. `base_offset` is the offset of `data[0]` in the +/// buffer tile locations are reported against. +pub(crate) fn parse_tile_group( + data: &[u8], + base_offset: usize, + info: &TileInfo, +) -> Result { + let num_tiles = info.num_tiles(); + if num_tiles == 0 { + return Err(PixelForgeError::InvalidInput( + "AV1: tile group for a frame with no tiles".to_string(), + )); + } + let mut r = BitReader::new(data); + let mut tg = TileGroup { + tile_start_and_end_present_flag: false, + tg_start: 0, + tg_end: (num_tiles - 1) as u16, + tiles: Vec::new(), + }; + if num_tiles > 1 { + tg.tile_start_and_end_present_flag = r.flag()?; + } + if tg.tile_start_and_end_present_flag { + let tile_bits = info.tile_cols_log2 as u32 + info.tile_rows_log2 as u32; + tg.tg_start = r.f(tile_bits)? as u16; + tg.tg_end = r.f(tile_bits)? as u16; + if tg.tg_start > tg.tg_end || tg.tg_end as usize >= num_tiles { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: tile group covers tiles {}..={} of {num_tiles}", + tg.tg_start, tg.tg_end + ))); + } + } + r.byte_alignment()?; + let mut pos = r.byte_position(); + let size_bytes = info.tile_size_bytes() as usize; + for tile_num in tg.tg_start..=tg.tg_end { + let size = if tile_num == tg.tg_end { + data.len() - pos + } else { + if data.len() - pos < size_bytes { + return Err(PixelForgeError::InvalidInput( + "AV1: tile group ends inside a tile size".to_string(), + )); + } + let mut sr = BitReader::new(&data[pos..pos + size_bytes]); + let size = sr.le(size_bytes as u32)? as usize + 1; + pos += size_bytes; + if size > data.len() - pos { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: tile {tile_num} of {size} bytes overruns its tile group" + ))); + } + size + }; + if size == 0 { + return Err(PixelForgeError::InvalidInput(format!( + "AV1: tile {tile_num} is empty" + ))); + } + tg.tiles.push(TileLocation { + offset: base_offset + pos, + size, + }); + pos += size; + } + Ok(tg) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn uniform_grid_for_320x240() { + // 320x240 is 80x60 mode-info units: 5x4 superblocks of 64x64. + // One increment of each log2: 2x2 tiles of 3 and 2 superblocks. + // Then context_update_tile_id (2 bits) and tile_size_bytes_minus_1. + // Bits: 1 1 0 | 1 0 | 11 | 01, then padding. + let bits = [0b1101_0110, 0b1000_0000]; + let mut r = BitReader::new(&bits); + let info = parse_tile_info(&mut r, false, 80, 60).unwrap(); + assert_eq!((info.tile_cols, info.tile_rows), (2, 2)); + assert_eq!(info.mi_col_starts, vec![0, 48, 80]); + assert_eq!(info.mi_row_starts, vec![0, 32, 60]); + assert_eq!(info.width_in_sbs_minus_1, vec![2, 1]); + assert_eq!(info.height_in_sbs_minus_1, vec![1, 1]); + assert_eq!(info.context_update_tile_id, 3); + assert_eq!(info.tile_size_bytes(), 2); + } + + #[test] + fn tile_group_sizes_and_offsets() { + let info = TileInfo { + tile_cols: 2, + tile_rows: 1, + tile_cols_log2: 1, + tile_size_bytes_minus_1: 1, + ..Default::default() + }; + // No start/end, aligned; tile 0 is 3 bytes (size field 2), tile 1 + // takes the remaining 2 bytes. + let data = [0x00, 0x02, 0x00, 1, 2, 3, 4, 5]; + let tg = parse_tile_group(&data, 100, &info).unwrap(); + assert_eq!( + tg.tiles, + vec![ + TileLocation { + offset: 103, + size: 3 + }, + TileLocation { + offset: 106, + size: 2 + } + ] + ); + // A tile size that overruns the OBU is an error, not a panic. + let bad = [0x00, 0x20, 0x00, 1]; + assert!(parse_tile_group(&bad, 0, &info).is_err()); + } +} diff --git a/src/decoder/common.rs b/src/decoder/common.rs index d618365c..bfabd236 100644 --- a/src/decoder/common.rs +++ b/src/decoder/common.rs @@ -22,8 +22,11 @@ use std::sync::Arc; use ash::vk; use crate::decoder::DecodedFrame; -use crate::decoder::frames::SlotPins; +use crate::decoder::frames::{DecodedPicture, SlotPins}; use crate::decoder::pipeline::DecodePipeline; +use crate::decoder::plan::SessionPlan; +use crate::decoder::profile::DecodeProfile; +use crate::decoder::record::{DecodeRecord, RecordCodec}; use crate::encoder::{BitDepth, ChromaFormat}; use crate::error::{PixelForgeError, Result}; use crate::video::{ @@ -32,84 +35,6 @@ use crate::video::{ }; use crate::vulkan::VideoContext; -/// Annex B start code prefixed to each slice handed to the driver. -/// -/// Three bytes, not four. The leading zero byte of a 4-byte start code is legal -/// Annex B, and RADV decodes either form, but Intel's ANV decodes almost nothing -/// when a slice offset points at that extra zero: it reports no error and -/// returns a picture with a handful of macroblocks in it. ffmpeg's Vulkan -/// decoder uses three bytes for the same reason. -pub(crate) const START_CODE: [u8; 3] = [0, 0, 1]; - -/// Query decode capabilities for a profile. -/// -/// `caps` is supplied by the caller already chained with the codec-specific -/// capability struct, since only the codec knows which one applies. -pub(crate) fn query_decode_caps( - context: &VideoContext, - profile_info: &vk::VideoProfileInfoKHR, - caps: &mut vk::VideoCapabilitiesKHR, -) -> Result<()> { - let video_queue_instance = - ash::khr::video_queue::Instance::load(context.entry(), context.instance()); - let result = unsafe { - (video_queue_instance - .fp() - .get_physical_device_video_capabilities_khr)( - context.physical_device(), - profile_info, - caps, - ) - }; - if result != vk::Result::SUCCESS { - return Err(PixelForgeError::NoSuitableDevice(format!( - "decode profile not supported: {:?}", - result - ))); - } - Ok(()) -} - -/// What a codec decided the session should look like. -/// -/// The codec derives these from its own parameter sets and the device caps; the -/// generic layer turns them into Vulkan objects. -pub(crate) struct SessionPlan { - /// Coded dimensions, already aligned to the device's picture granularity. - pub coded_width: u32, - pub coded_height: u32, - pub picture_format: vk::Format, - pub bit_depth: BitDepth, - pub chroma_format: ChromaFormat, - /// DPB slots: references, the picture being decoded, and `spare_slots`. - pub slot_count: usize, - /// How many of `slot_count` are spare capacity for pictures pinned past the - /// decode that produced them: those held for display-order reordering, and - /// those handed to the caller. Zero means every emitted picture has to be - /// copied out instead of pinned in place. - pub spare_slots: usize, - /// References the driver may use for a single picture, which is - /// `slot_count` minus the current picture and the output reservation. - pub max_active_references: u32, - /// Driver decodes straight into the DPB image (`DPB_AND_OUTPUT_COINCIDE`). - pub coincide: bool, - /// A decoded picture may be handed to the caller in place, pinned in its - /// DPB slot, rather than copied out. See [`DecodeSession::pinnable`]. - pub pinnable: bool, - /// `dpb_usage` includes `SAMPLED`, so handed-out frames can be sampled. - pub sampleable: bool, - /// Picture images are created with `MUTABLE_FORMAT`, so a consumer can view - /// their planes separately. See [`DecodeSession::plane_views`]. - pub plane_views: bool, - /// Creation flags for picture images, from what the driver reported. - pub image_flags: vk::ImageCreateFlags, - /// Formats views of a picture image may use. Empty unless `plane_views`. - pub view_formats: Vec, - /// One layered DPB image rather than one image per slot. - pub use_layered_dpb: bool, - pub dpb_usage: vk::ImageUsageFlags, -} - /// One DPB image and everything that lives and dies with it. /// /// Held behind an `Arc` shared with every frame decoded into it, so a session @@ -211,6 +136,17 @@ pub(crate) struct DecodeSession { pub plane_views: bool, /// Distinct decode output image, only when `!coincide`. pub output_image: Option<(vk::Image, vk::DeviceMemory, vk::ImageView)>, + /// One film grain output image per DPB slot, or none. + /// + /// Vulkan applies AV1 film grain only to a decode output that is not the + /// DPB slot, since the reference must stay grain-free. A frame with grain + /// is therefore decoded into its slot's grain image as well, and handed + /// out from there. One per slot rather than one in all, because + /// `show_existing_frame` can show a frame again long after its decode, and + /// it has to show the same grain. + pub grain_outputs: Vec>, + /// The profile everything in this session was created with. + pub profile: DecodeProfile, } impl DecodeSession { /// The image and array layer holding DPB slot `slot`. @@ -221,6 +157,17 @@ impl DecodeSession { (entry.image, layer) } + /// The film grain output image of `slot`. Only for a session with film + /// grain. + pub fn grain_entry(&self, slot: u8) -> &Arc { + &self.grain_outputs[slot as usize] + } + + /// The view of `slot`'s film grain output image. + pub fn grain_view(&self, slot: u8) -> vk::ImageView { + self.grain_outputs[slot as usize].views[0] + } + /// The image backing `slot`, the array layer within it, and its view. pub fn dpb_entry(&self, slot: u8) -> (&Arc, u32, vk::ImageView) { if self.use_layered_dpb { @@ -382,11 +329,13 @@ impl DecoderCommon { pub fn create_session( &mut self, plan: &SessionPlan, - profile_info: &vk::VideoProfileInfoKHR, - std_header_version: &vk::ExtensionProperties, + profile: DecodeProfile, make_params: impl FnOnce(&Self, vk::VideoSessionKHR) -> Result, ) -> Result<()> { self.destroy_session(); + let profile_info = profile.info(); + self.bitstream_offset_alignment = plan.bitstream_offset_alignment; + self.bitstream_size_alignment = plan.bitstream_size_alignment; let session_create_info = vk::VideoSessionCreateInfoKHR::default() .queue_family_index(self.decode_queue_family) @@ -400,7 +349,7 @@ impl DecoderCommon { .reference_picture_format(plan.picture_format) .max_dpb_slots(plan.slot_count as u32) .max_active_reference_pictures(plan.max_active_references) - .std_header_version(std_header_version); + .std_header_version(&plan.std_header_version); let session = unsafe { self.video_queue_fn @@ -474,6 +423,32 @@ impl DecoderCommon { )?) }; + let grain_outputs = if plan.film_grain { + let mut usage = + vk::ImageUsageFlags::VIDEO_DECODE_DST_KHR | vk::ImageUsageFlags::TRANSFER_SRC; + if plan.sampleable { + usage |= vk::ImageUsageFlags::SAMPLED; + } + let grain_params = VideoImageParams { + usage, + ..dpb_params + }; + (0..plan.slot_count) + .map(|_| { + let (image, memory, view) = + create_video_image(&self.context, &grain_params, profile_info)?; + Ok(Arc::new(DpbImage { + context: self.context.clone(), + image, + memory, + views: vec![view], + })) + }) + .collect::>>()? + } else { + Vec::new() + }; + let session_params = make_params(self, session)?; // A new set of images, so a new generation. Frames from the previous @@ -498,7 +473,28 @@ impl DecoderCommon { sampleable: plan.sampleable, plane_views: plan.plane_views, output_image, + grain_outputs, + profile, }); + + tracing::debug!( + "{} decode session: {}x{} {:?}, {} DPB slots ({} spare), \ + layered={}, coincide={}, pinnable={}, sampleable={}, \ + plane_views={}, bitstream alignment {}/{} (offset/size)", + plan.codec, + plan.coded_width, + plan.coded_height, + plan.picture_format, + plan.slot_count, + plan.spare_slots, + plan.use_layered_dpb, + plan.coincide, + plan.pinnable, + plan.sampleable, + plan.plane_views, + plan.bitstream_offset_alignment, + plan.bitstream_size_alignment + ); Ok(()) } @@ -538,18 +534,22 @@ impl DecoderCommon { } } - /// Copy a picture's slices into the staging buffer, each prefixed with a - /// start code, and report the buffer range plus each slice's offset. - pub fn stage_slices( - &mut self, - slices: &[&[u8]], - profile_info: &vk::VideoProfileInfoKHR, - ) -> Result<(u64, Vec)> { - let total: usize = slices.iter().map(|s| s.len() + START_CODE.len()).sum(); + /// Copy a picture's slices into the staging buffer, each preceded by + /// `prefix` (a start code for H.26x, nothing for AV1), and report the + /// buffer range plus each slice's offset. + pub fn stage_slices(&mut self, slices: &[&[u8]], prefix: &[u8]) -> Result<(u64, Vec)> { + let total: usize = slices.iter().map(|s| s.len() + prefix.len()).sum(); let aligned_total = crate::video::align_up(total as u32, self.bitstream_size_alignment as u32) as usize; - self.pipeline - .ensure_bitstream_capacity(&self.context, aligned_total, profile_info)?; + // Borrow the session by field, so the pipeline can be borrowed mutably. + let session = self.session.as_ref().ok_or_else(|| { + PixelForgeError::InvalidInput("decode: no active session".to_string()) + })?; + self.pipeline.ensure_bitstream_capacity( + &self.context, + aligned_total, + session.profile.info(), + )?; let dst = self.pipeline.current().bitstream_ptr; let mut offsets = Vec::with_capacity(slices.len()); @@ -557,12 +557,8 @@ impl DecoderCommon { for slice in slices { offsets.push(cursor as u32); unsafe { - std::ptr::copy_nonoverlapping( - START_CODE.as_ptr(), - dst.add(cursor), - START_CODE.len(), - ); - cursor += START_CODE.len(); + std::ptr::copy_nonoverlapping(prefix.as_ptr(), dst.add(cursor), prefix.len()); + cursor += prefix.len(); std::ptr::copy_nonoverlapping(slice.as_ptr(), dst.add(cursor), slice.len()); cursor += slice.len(); } @@ -635,6 +631,49 @@ impl DecoderCommon { unsafe { sync2.cmd_pipeline_barrier2(self.decode_command_buffer(), &dependency) }; } + /// Transition `slot`'s film grain output image into the layout + /// `vkCmdDecodeVideo` writes it in. Its old contents are discarded: the + /// slot is free, so nothing still shows them. + pub fn record_grain_barrier(&self, slot: u8) { + let session = self.session.as_ref().expect("active session"); + let barrier = vk::ImageMemoryBarrier2::default() + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .src_access_mask(vk::AccessFlags2::empty()) + .dst_stage_mask(vk::PipelineStageFlags2::VIDEO_DECODE_KHR) + .dst_access_mask(vk::AccessFlags2::VIDEO_DECODE_WRITE_KHR) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(self.output_layout()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(session.grain_entry(slot).image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let barriers = [barrier]; + let dependency = vk::DependencyInfo::default().image_memory_barriers(&barriers); + unsafe { + self.context + .sync2() + .cmd_pipeline_barrier2(self.decode_command_buffer(), &dependency) + }; + } + + /// Point `picture` at its slot's film grain output image instead of the + /// slot itself. + pub fn use_grain_output(&self, picture: &mut DecodedPicture) { + let session = self.session.as_ref().expect("active session"); + let entry = session.grain_entry(picture.slot); + picture.image = entry.image; + picture.image_view = entry.views[0]; + picture.layout = self.output_layout(); + picture.array_layer = 0; + picture.film_grain = true; + } + /// The decode command buffer of the picture currently being recorded. pub fn decode_command_buffer(&self) -> vk::CommandBuffer { self.pipeline.current().decode_command_buffer @@ -677,6 +716,80 @@ impl DecoderCommon { .submit_decode(&device, self.context.sync2(), queue) } + /// Record `vkCmdBeginVideoCoding`, `vkCmdDecodeVideo` and + /// `vkCmdEndVideoCoding` from a linked `record`, resetting the session + /// first if this is its first decode. + pub fn record_coding(&self, record: &DecodeRecord) { + let command_buffer = self.decode_command_buffer(); + // A session's first decode sees no active slot. + let first_use = self + .session + .as_ref() + .is_none_or(|s| !s.dpb_slot_active.iter().any(|&a| a)); + unsafe { + self.video_queue_fn + .cmd_begin_video_coding(command_buffer, record.begin_info()); + // On the first use of the session, all DPB slots must be reset. + if first_use { + let control = vk::VideoCodingControlInfoKHR::default() + .flags(vk::VideoCodingControlFlagsKHR::RESET); + self.video_queue_fn + .cmd_control_video_coding(command_buffer, &control); + } + self.video_decode_fn + .cmd_decode_video(command_buffer, record.decode_info()); + self.video_queue_fn + .cmd_end_video_coding(command_buffer, &vk::VideoEndCodingInfoKHR::default()); + } + } + + /// Mark `slot` as decoded into, and describe the picture now in it for + /// output. Call after the picture's decode is submitted. + /// + /// `width` and `height` are the visible (cropped) size. + pub fn decoded_picture( + &mut self, + slot: u8, + width: u32, + height: u32, + pts: u64, + display_order: i32, + is_keyframe: bool, + ) -> DecodedPicture { + let picture_layout = self.picture_layout(); + let output_layout = self.output_layout(); + let generation = self.generation; + let session = self.session.as_mut().expect("a picture was just decoded"); + session.dpb_slot_active[slot as usize] = true; + let (image, image_view, layout, array_layer) = if session.coincide { + let (image, layer) = session.dpb_image_for_slot(slot); + (image, session.dpb_entry(slot).2, picture_layout, layer) + } else { + let (image, _, view) = session + .output_image + .expect("non-coincide implies output image"); + (image, view, output_layout, 0) + }; + DecodedPicture { + slot, + image, + image_view, + layout, + array_layer, + chroma_format: session.chroma_format, + bit_depth: session.bit_depth, + generation, + width, + height, + coded_width: session.coded_width, + coded_height: session.coded_height, + pts, + display_order, + is_keyframe, + film_grain: false, + } + } + /// Close off the current picture and hand `frames` to the completion thread. pub fn end_picture(&mut self, frames: Vec) { self.pipeline.end_picture(frames); diff --git a/src/decoder/driver.rs b/src/decoder/driver.rs new file mode 100644 index 00000000..fbd48081 --- /dev/null +++ b/src/decoder/driver.rs @@ -0,0 +1,253 @@ +//! The decode flow every codec shares: framing the input, waiting for a +//! keyframe, reordering into display order, and the end of the stream. +//! +//! A codec implements [`CodecDecoder`]: how to split its bitstream into coded +//! pictures and how to decode one. [`StreamDecoder`] drives it and implements +//! the codec-erased [`DecoderApi`](crate::decoder::DecoderApi) once for all +//! codecs. + +use ash::vk; +use tracing::debug; + +use crate::decoder::common::DecoderCommon; +use crate::decoder::frames::{DecodedPicture, ReorderBuffer}; +use crate::decoder::{DecodeConfig, DecodeStatus, DecoderApi, FrameReceiver, Framing}; +use crate::error::Result; + +/// What a codec does; everything else is [`StreamDecoder`]'s. +pub(crate) trait CodecDecoder: Send { + /// One coded picture, as [`Self::split_units`] gathers it from the input. + type Unit<'a>; + + /// The codec's name, for messages: "H.264". + const NAME: &'static str; + + /// The shared Vulkan state, which the codec owns. + fn common(&self) -> &DecoderCommon; + fn common_mut(&mut self) -> &mut DecoderCommon; + + /// How many leading bytes of `buffer` hold coded pictures known to be + /// complete. + /// + /// A coded picture is only known to have ended once the next one starts, + /// so everything from the last picture start onward may still be waiting + /// for more data. Zero means nothing in `buffer` can be decoded yet. + fn complete_prefix(buffer: &[u8]) -> usize; + + /// Split `data`, which ends on a picture boundary, into coded pictures, + /// absorbing any parameter sets on the way. + /// + /// Returns the pictures plus, if some data was skipped because state it + /// needs has not been seen (parameter sets sent before the stream was + /// joined), the reason a keyframe is needed. + fn split_units<'a>(&mut self, data: &'a [u8]) -> Result<(Vec>, Option)>; + + /// Whether decoding can start at `unit`. + fn is_keyframe(unit: &Self::Unit<'_>) -> bool; + + /// Decode one coded picture. + fn decode_unit(&mut self, unit: &Self::Unit<'_>, pts: u64) -> Result; + + /// How many pictures the active stream may hold back for display-order + /// reordering. + fn reorder_depth(&self) -> usize; +} + +/// What [`CodecDecoder::decode_unit`] did with a coded picture. +pub(crate) enum Decoded { + /// Decoded, and to be output. + Shown(DecodedPicture), + /// Decoded, but not output: an AV1 frame with `show_frame` 0, or an H.265 + /// picture with `pic_output_flag` 0. + Hidden, + /// Not decoded, and that is normal: an H.265 RASL picture after the CRA + /// that started decoding, which references pictures from before it. + Skipped, + /// Not decoded, because it references state the decoder does not have: + /// pictures or parameter sets from before the stream was joined, or lost + /// since. The reason names what is missing. + NeedsKeyframe(String), +} + +/// Drives a [`CodecDecoder`]. +pub(crate) struct StreamDecoder { + codec: C, + + /// Display-order reordering, which holds pictures by pinning their DPB + /// slots rather than by copying them out. + reorder: ReorderBuffer, + + /// How input is framed, which decides whether the end of a `decode` call is + /// also the end of a coded picture. + framing: Framing, + /// Bytes of a coded picture that has not been seen to end yet, held over + /// from earlier `decode` calls. Only ever non-empty in + /// [`Framing::ByteStream`]. + carry: Vec, + /// The `pts` of the call that delivered the first byte of `carry`, so a + /// picture is timestamped by when it started arriving rather than by which + /// call happened to complete it. + carry_pts: u64, + + /// Whether decoding is waiting for a keyframe before it can produce output. + /// True until the first keyframe is decoded: other pictures reference state + /// that does not exist yet, so they are skipped and the caller is told to + /// request a keyframe. This is the normal state when joining a stream. + awaiting_keyframe: bool, +} + +impl StreamDecoder { + pub(crate) fn new(codec: C, config: &DecodeConfig) -> Self { + Self { + codec, + reorder: ReorderBuffer::new(), + framing: config.framing, + carry: Vec::new(), + carry_pts: 0, + awaiting_keyframe: true, + } + } + + /// Framing for input that may cut anywhere. + /// + /// A coded picture is only known to have ended once the next one starts, so + /// everything up to the last picture start is complete and decodable, and + /// everything from it onward is held over for the next call. `finish` ends + /// the final picture. + fn decode_byte_stream(&mut self, data: &[u8], pts: u64) -> Result { + let carried = self.carry.len(); + let mut buffer = std::mem::take(&mut self.carry); + buffer.extend_from_slice(data); + + // Nothing before the last picture start can still be waiting for more + // data. With no picture start at all, nothing is complete yet. + let split = C::complete_prefix(&buffer); + + let result = if split > 0 { + self.decode_frames(&buffer[..split], self.carry_pts) + } else { + // Not enough bytes to complete a picture yet. + Ok(DecodeStatus::Buffered) + }; + + // The held-over picture is timestamped by the call its first byte came + // in on, which is this one only if the split consumed everything the + // previous calls had left. + if split >= carried { + self.carry_pts = pts; + } + buffer.drain(..split); + self.carry = buffer; + result + } + + /// Decode input already known to end on a picture boundary. + /// + /// Frames go to the consumer as each picture's work is handed to the + /// completion thread, so a call decoding many pictures does not hold their + /// frames until it returns. + fn decode_frames(&mut self, data: &[u8], pts: u64) -> Result { + // What is missing when a picture cannot be decoded yet; set only while + // no frame has been produced, so a keyframe later in the same buffer + // still yields output. Seeded with data skipped during splitting. + let (units, mut awaiting) = self.codec.split_units(data)?; + // Whether any picture actually reached the GPU, which decides between + // reporting a decode and asking for a keyframe. + let mut decoded_any = false; + + for unit in units { + let is_keyframe = C::is_keyframe(&unit); + + // A picture before the first keyframe references pictures and + // state that do not exist yet. Skip it and ask for a keyframe + // rather than decoding garbage. + if self.awaiting_keyframe && !is_keyframe { + awaiting.get_or_insert_with(|| { + "no keyframe decoded yet; picture cannot be decoded".to_string() + }); + continue; + } + + let ready = match self.codec.decode_unit(&unit, pts)? { + Decoded::Shown(picture) => { + let depth = self.codec.reorder_depth(); + self.reorder + .push(self.codec.common_mut(), &picture, depth)? + } + Decoded::Hidden => Vec::new(), + Decoded::Skipped => continue, + Decoded::NeedsKeyframe(reason) => { + awaiting.get_or_insert(reason); + continue; + } + }; + decoded_any = true; + // A decoded keyframe re-establishes a valid decode point. + if is_keyframe { + self.awaiting_keyframe = false; + } + // The picture's submissions are complete as far as recording goes; + // hand its slot and frames to the completion thread and move on to + // the next slot. + self.codec.common_mut().end_picture(ready); + } + + if decoded_any { + return Ok(DecodeStatus::Decoded); + } + // Only ask for a keyframe when nothing was decoded: if one arrived + // later in the same buffer, its pictures take priority. + if let Some(reason) = awaiting { + debug!("{} decode: awaiting keyframe: {}", C::NAME, reason); + return Ok(DecodeStatus::NeedsKeyframe); + } + Ok(DecodeStatus::Buffered) + } +} + +impl DecoderApi for StreamDecoder { + fn decode(&mut self, data: &[u8], pts: u64) -> Result { + match self.framing { + // The caller guarantees the chunk ends on a picture boundary, so it + // can go straight to the decoder with no buffering and no copy. + Framing::FrameAligned => self.decode_frames(data, pts), + Framing::ByteStream => self.decode_byte_stream(data, pts), + } + } + + /// Decode what is still buffered and emit the pictures held for + /// reordering. Call at end of stream. + fn finish(&mut self) -> Result<()> { + // In byte-stream framing the tail is a picture that was never seen to + // end because nothing followed it. End of stream is what ends it. + if !self.carry.is_empty() { + let tail = std::mem::take(&mut self.carry); + let pts = self.carry_pts; + self.decode_frames(&tail, pts)?; + } + let frames = self.reorder.flush(); + self.codec.common_mut().finish_stream(frames); + Ok(()) + } + + fn take_frame_receiver(&mut self) -> Option { + self.codec.common_mut().frames_rx.take() + } + + fn picture_format(&self) -> Option { + self.codec + .common() + .session + .as_ref() + .map(|s| s.picture_format) + } +} + +impl Drop for StreamDecoder { + fn drop(&mut self) { + // The reorder pool's images must be freed while the codec's + // `DecoderCommon` (and its device) is still alive. Fields drop after + // this, so the codec goes afterwards. + self.reorder.destroy(self.codec.common()); + } +} diff --git a/src/decoder/frames.rs b/src/decoder/frames.rs index a2b32dd5..4ab771db 100644 --- a/src/decoder/frames.rs +++ b/src/decoder/frames.rs @@ -42,6 +42,8 @@ pub(crate) struct DecodedPicture { pub pts: u64, pub display_order: i32, pub is_keyframe: bool, + /// The picture is in its slot's film grain output image, not in the slot. + pub film_grain: bool, } /// An image in the frame pool, by index. @@ -54,48 +56,69 @@ pub(crate) struct PoolIndex(usize); /// so the codec must not decode over it. Releases come from [`FramePin::drop`] /// on whatever thread the frame died on, which is why the decoder waits on the /// condition variable rather than polling. +/// +/// A slot can be pinned more than once: AV1 can show the same picture again +/// (`show_existing_frame`), and each frame that shows it holds its own pin. +/// The slot stays pinned until the last of them is released. #[derive(Debug, Default)] pub(crate) struct SlotPins { - /// One bit per DPB slot. - pinned: Mutex, + state: Mutex, released: Condvar, } +#[derive(Debug, Default)] +struct PinState { + /// Pins held on each DPB slot. + counts: [u16; MAX_PINNABLE_SLOTS], + /// Releases so far, so a waiter can tell that one happened. + releases: u64, +} + +/// The most DPB slots any codec uses: 16 references plus the current picture +/// for H.264 and H.265, with room to spare. +const MAX_PINNABLE_SLOTS: usize = 32; + impl SlotPins { pub fn is_pinned(&self, slot: u8) -> bool { - *self.pinned.lock().unwrap() & (1 << slot) != 0 + self.state.lock().unwrap().counts[slot as usize] != 0 } pub fn any_pinned(&self) -> bool { - *self.pinned.lock().unwrap() != 0 + self.state.lock().unwrap().counts.iter().any(|&c| c != 0) } /// How many slots are pinned right now, across everything holding one: /// pictures awaiting their turn in display order, frames on their way to - /// the caller, and frames the caller has not dropped. + /// the caller, and frames the caller has not dropped. A slot pinned more + /// than once counts once, since it holds one slot. pub fn count(&self) -> usize { - self.pinned.lock().unwrap().count_ones() as usize + let state = self.state.lock().unwrap(); + state.counts.iter().filter(|&&c| c != 0).count() } pub fn pin(&self, slot: u8) { - *self.pinned.lock().unwrap() |= 1 << slot; + self.state.lock().unwrap().counts[slot as usize] += 1; } pub fn release(&self, slot: u8) { - *self.pinned.lock().unwrap() &= !(1 << slot); + let mut state = self.state.lock().unwrap(); + let count = &mut state.counts[slot as usize]; + debug_assert!(*count > 0, "released slot {slot} was not pinned"); + *count = count.saturating_sub(1); + state.releases += 1; self.released.notify_all(); } - /// Block until at least one pinned slot is released. Returns immediately if + /// Block until at least one pin is released. Returns immediately if /// nothing is pinned, since then no release can ever come. pub fn wait_for_release(&self) { - let mut pinned = self.pinned.lock().unwrap(); - let before = *pinned; - if before == 0 { + let mut state = self.state.lock().unwrap(); + if state.counts.iter().all(|&c| c == 0) { return; } - while *pinned == before { - pinned = self.released.wait(pinned).unwrap(); + let before = state.releases; + while state.releases == before { + state = self.released.wait(state).unwrap(); } } } @@ -429,7 +452,11 @@ impl ReorderBuffer { // holds frames gets copies rather than a decoder that cannot proceed. if session.pinnable && session.slot_pins.count() < session.spare_slots { session.slot_pins.pin(picture.slot); - let (entry, _, _) = session.dpb_entry(picture.slot); + let entry = if picture.film_grain { + session.grain_entry(picture.slot) + } else { + session.dpb_entry(picture.slot).0 + }; return Ok(Retained::Slot { pin: FramePin::DpbSlot { slot: picture.slot, @@ -610,3 +637,21 @@ fn create_pool_image( Ok((image, memory)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_slot_pinned_twice_stays_pinned_until_both_release() { + let pins = SlotPins::default(); + pins.pin(3); + pins.pin(3); + assert_eq!(pins.count(), 1, "one slot is held, however often"); + pins.release(3); + assert!(pins.is_pinned(3)); + pins.release(3); + assert!(!pins.is_pinned(3)); + assert!(!pins.any_pinned()); + } +} diff --git a/src/decoder/h264/mod.rs b/src/decoder/h264/mod.rs index 7c61af60..6815eb65 100644 --- a/src/decoder/h264/mod.rs +++ b/src/decoder/h264/mod.rs @@ -14,10 +14,6 @@ mod dpb; mod record; mod session; -use crate::decoder::{DecodeStatus, FrameReceiver}; -use crate::error::Result; -use ash::vk; - pub(crate) use session::H264Decoder; /// Byte offsets at which each coded picture begins in an Annex B stream. @@ -58,21 +54,3 @@ pub(crate) fn picture_starts(stream: &[u8]) -> Vec { pub(crate) fn complete_prefix(buffer: &[u8]) -> usize { picture_starts(buffer).last().copied().unwrap_or(0) } - -impl crate::decoder::DecoderApi for H264Decoder { - fn decode(&mut self, data: &[u8], pts: u64) -> Result { - H264Decoder::decode(self, data, pts) - } - - fn finish(&mut self) -> Result<()> { - H264Decoder::finish(self) - } - - fn take_frame_receiver(&mut self) -> Option { - H264Decoder::take_frame_receiver(self) - } - - fn picture_format(&self) -> Option { - H264Decoder::picture_format(self) - } -} diff --git a/src/decoder/h264/parser.rs b/src/decoder/h264/parser.rs index 66559f4a..9850a723 100644 --- a/src/decoder/h264/parser.rs +++ b/src/decoder/h264/parser.rs @@ -11,6 +11,7 @@ //! Accordingly the slice-header parser stops right after the POC syntax //! elements; nothing later in the header is needed on the host side. +use crate::decoder::annexb; use crate::decoder::bitreader::BitReader; use crate::error::{PixelForgeError, Result}; @@ -78,62 +79,17 @@ pub fn iter_nal_units(data: &[u8]) -> impl Iterator> { pub(crate) fn iter_nal_units_with_offsets( data: &[u8], ) -> impl Iterator)> { - NalIterator { data, pos: 0 } -} - -struct NalIterator<'a> { - data: &'a [u8], - pos: usize, -} - -/// Find the next start code at or after `from`. Returns (start_code_pos, payload_pos). -fn find_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> { - let mut i = from; - while i + 3 <= data.len() { - if data[i] == 0 && data[i + 1] == 0 { - if data[i + 2] == 1 { - return Some((i, i + 3)); - } - if i + 4 <= data.len() && data[i + 2] == 0 && data[i + 3] == 1 { - return Some((i, i + 4)); - } - } - i += 1; - } - None -} - -impl<'a> Iterator for NalIterator<'a> { - type Item = (usize, NalUnit<'a>); - - fn next(&mut self) -> Option<(usize, NalUnit<'a>)> { - let (start_code_pos, payload_start) = find_start_code(self.data, self.pos)?; - let end = match find_start_code(self.data, payload_start) { - Some((next_start, _)) => next_start, - None => self.data.len(), - }; - self.pos = end; - - // Trim trailing zero padding before the next start code. - let mut trimmed_end = end; - while trimmed_end > payload_start && self.data[trimmed_end - 1] == 0 { - trimmed_end -= 1; - } - if trimmed_end <= payload_start { - return self.next(); - } - - let nal = &self.data[payload_start..trimmed_end]; + annexb::iter_nal_units(data).map(|(offset, nal)| { let header = nal[0]; - Some(( - start_code_pos, + ( + offset, NalUnit { nal_type: NalType::from_raw(header & 0x1F), ref_idc: (header >> 5) & 0x3, data: nal, }, - )) - } + ) + }) } /// Parsed H.264 sequence parameter set (the subset Vulkan decode needs). diff --git a/src/decoder/h264/record.rs b/src/decoder/h264/record.rs index 58bb1f8b..0f2c5b51 100644 --- a/src/decoder/h264/record.rs +++ b/src/decoder/h264/record.rs @@ -1,244 +1,29 @@ -//! Storage for the structures one H.264 decode command is recorded from. -//! -//! NVIDIA's decoder does not copy `VkVideoDecodeInfoKHR` and the structures -//! hanging off it when the command is recorded: it reads them again at -//! `vkQueueSubmit`. They therefore have to stay alive and unmoved until the -//! submission has finished, which a stack frame in the recording function does -//! not guarantee. Left on the stack they are garbage by submit time, which -//! crashed the driver or made it decode B-pictures from stale reference data. -//! -//! A [`DecodeRecord`] owns all of them. There is one per pipeline slot, and it -//! is rebuilt only once that slot's previous submission has completed. +//! The H.264 structures of a decode command. use ash::vk; -use ash::vk::TaggedStructure; use ash::vk::native::{StdVideoDecodeH264PictureInfo, StdVideoDecodeH264ReferenceInfo}; -use super::dpb::MAX_DPB_SLOTS; +use crate::decoder::record::RecordCodec; -/// Most references one picture can have: every DPB slot but the one being set up. -const MAX_REFS: usize = MAX_DPB_SLOTS; +/// H.264's part of a [`DecodeRecord`](crate::decoder::record::DecodeRecord). +pub(super) struct H264Record; -/// Everything one decode command points at. -/// -/// The Vulkan structures here point at each other and at the plain data beside -/// them, so the whole is a self-referential value. That is sound because it lives in -/// heap storage it is never moved out of once built, and every pointer is rebuilt -/// by [`DecodeRecord::link`] after the last write to what it points at. The -/// `'static` lifetimes on the fields are internal: nothing outside this module -/// can obtain a reference that outlives the record. -/// -/// Every collection is a fixed-size array holding `ref_count` live entries, so -/// nothing is ever reallocated and an element's address does not change however -/// the record is filled. -pub(super) struct DecodeRecord { - // Plain data. - slice_offsets: Vec, - ref_count: usize, - /// DPB slot and image view of each reference. - refs: [(u8, vk::ImageView); MAX_REFS], - ref_std: [StdVideoDecodeH264ReferenceInfo; MAX_REFS], - setup_std: StdVideoDecodeH264ReferenceInfo, - pic_std: StdVideoDecodeH264PictureInfo, +impl RecordCodec for H264Record { + type RefStd = StdVideoDecodeH264ReferenceInfo; + type DpbSlot = vk::VideoDecodeH264DpbSlotInfoKHR<'static>; + type Picture = StdVideoDecodeH264PictureInfo; + type PictureInfo = vk::VideoDecodeH264PictureInfoKHR<'static>; - // Picture resources. - ref_resources: [vk::VideoPictureResourceInfoKHR<'static>; MAX_REFS], - setup_resource: vk::VideoPictureResourceInfoKHR<'static>, - output_resource: vk::VideoPictureResourceInfoKHR<'static>, - - // Codec-specific slot info. The begin and decode commands each chain their - // own copy, since chaining takes the structure mutably. - begin_dpb: [vk::VideoDecodeH264DpbSlotInfoKHR<'static>; MAX_REFS], - decode_dpb: [vk::VideoDecodeH264DpbSlotInfoKHR<'static>; MAX_REFS], - setup_dpb: vk::VideoDecodeH264DpbSlotInfoKHR<'static>, - - // Reference slots. `begin_slots` has room for the slot being set up too. - begin_slots: [vk::VideoReferenceSlotInfoKHR<'static>; MAX_REFS + 1], - decode_slots: [vk::VideoReferenceSlotInfoKHR<'static>; MAX_REFS], - setup_slot: vk::VideoReferenceSlotInfoKHR<'static>, - - // Top level. - pic_info: vk::VideoDecodeH264PictureInfoKHR<'static>, - begin_info: vk::VideoBeginCodingInfoKHR<'static>, - decode_info: vk::VideoDecodeInfoKHR<'static>, -} - -// SAFETY: the raw pointers inside the Vulkan structures only ever point into -// the same record, which is owned by one decoder and used from one thread at a -// time. -unsafe impl Send for DecodeRecord {} - -/// What [`DecodeRecord::link`] needs besides the references already pushed. -pub(super) struct LinkParams { - pub extent: vk::Extent2D, - pub session: vk::VideoSessionKHR, - pub session_params: vk::VideoSessionParametersKHR, - pub bitstream: vk::Buffer, - pub bitstream_range: u64, - /// DPB slot, and view of it, that the picture is reconstructed into. - pub setup_slot: u8, - pub setup_view: vk::ImageView, - /// The decode destination when it is not the DPB slot itself. - pub output_view: Option, - pub setup_std: StdVideoDecodeH264ReferenceInfo, - pub pic_std: StdVideoDecodeH264PictureInfo, -} - -/// Extend a borrow to any lifetime. -/// -/// # Safety -/// `value` must stay alive and in place for as long as the result is used. -unsafe fn stable<'a, T: ?Sized>(value: &T) -> &'a T { - unsafe { &*(value as *const T) } -} - -/// As [`stable`], for a mutable borrow. -/// -/// # Safety -/// As [`stable`], and nothing else may access `value` while the result is used. -unsafe fn stable_mut<'a, T: ?Sized>(value: &mut T) -> &'a mut T { - unsafe { &mut *(value as *mut T) } -} - -impl DecodeRecord { - pub(super) fn new() -> Self { - // SAFETY: the standard structures are plain C data, for which all-zero - // is a valid (if meaningless) value. - let zeroed_ref: StdVideoDecodeH264ReferenceInfo = unsafe { std::mem::zeroed() }; - let (setup_std, pic_std) = (zeroed_ref, unsafe { std::mem::zeroed() }); - Self { - slice_offsets: Vec::new(), - ref_count: 0, - refs: [(0, vk::ImageView::null()); MAX_REFS], - ref_std: [zeroed_ref; MAX_REFS], - setup_std, - pic_std, - ref_resources: [Default::default(); MAX_REFS], - setup_resource: Default::default(), - output_resource: Default::default(), - begin_dpb: [Default::default(); MAX_REFS], - decode_dpb: [Default::default(); MAX_REFS], - setup_dpb: Default::default(), - begin_slots: [Default::default(); MAX_REFS + 1], - decode_slots: [Default::default(); MAX_REFS], - setup_slot: Default::default(), - pic_info: Default::default(), - begin_info: Default::default(), - decode_info: Default::default(), - } - } - - /// Start a new picture, discarding the last one. - /// - /// Only call once the submission that used this record has completed. - pub(super) fn reset(&mut self, slice_offsets: &[u32]) { - self.slice_offsets.clear(); - self.slice_offsets.extend_from_slice(slice_offsets); - self.ref_count = 0; - } - - /// Add a reference picture, in the order the driver is to see it. - pub(super) fn push_reference( - &mut self, - slot: u8, - view: vk::ImageView, - std: StdVideoDecodeH264ReferenceInfo, - ) { - assert!(self.ref_count < MAX_REFS, "too many H.264 references"); - self.refs[self.ref_count] = (slot, view); - self.ref_std[self.ref_count] = std; - self.ref_count += 1; - } - - /// Build the structures from what has been pushed, wiring up every pointer. - /// - /// Order matters: the plain data and the structures pointed at are written - /// before the structures that point at them. - pub(super) fn link(&mut self, p: &LinkParams) { - let n = self.ref_count; - self.setup_std = p.setup_std; - self.pic_std = p.pic_std; - - let resource = |view: vk::ImageView| { - vk::VideoPictureResourceInfoKHR::default() - .coded_offset(vk::Offset2D { x: 0, y: 0 }) - .coded_extent(p.extent) - .base_array_layer(0) - .image_view_binding(view) - }; - for i in 0..n { - self.ref_resources[i] = resource(self.refs[i].1); - } - self.setup_resource = resource(p.setup_view); - self.output_resource = match p.output_view { - None => self.setup_resource, - Some(view) => resource(view), - }; - - // SAFETY (for every `stable`/`stable_mut` below): the target is a field - // of `self`, which is boxed and not written again until the next - // `reset`/`link`, and fixed arrays never move their elements. - unsafe { - for i in 0..n { - let info = vk::VideoDecodeH264DpbSlotInfoKHR::default() - .std_reference_info(stable(&self.ref_std[i])); - self.begin_dpb[i] = info; - self.decode_dpb[i] = info; - } - self.setup_dpb = vk::VideoDecodeH264DpbSlotInfoKHR::default() - .std_reference_info(stable(&self.setup_std)); - - for i in 0..n { - let slot = self.refs[i].0 as i32; - let resource = stable(&self.ref_resources[i]); - self.begin_slots[i] = vk::VideoReferenceSlotInfoKHR::default() - .slot_index(slot) - .picture_resource(resource) - .push(stable_mut(&mut self.begin_dpb[i])); - self.decode_slots[i] = vk::VideoReferenceSlotInfoKHR::default() - .slot_index(slot) - .picture_resource(resource) - .push(stable_mut(&mut self.decode_dpb[i])); - } - // The slot being reconstructed is listed to `begin` with slot index - // -1: it is not active until the decode's setup slot activates it, - // and a non-negative index here must name an already-active slot - // (VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239). -1 still binds - // its picture resource. - self.begin_slots[n] = vk::VideoReferenceSlotInfoKHR::default() - .slot_index(-1) - .picture_resource(stable(&self.setup_resource)); - self.setup_slot = vk::VideoReferenceSlotInfoKHR::default() - .slot_index(p.setup_slot as i32) - .picture_resource(stable(&self.setup_resource)) - .push(stable_mut(&mut self.setup_dpb)); - - self.pic_info = vk::VideoDecodeH264PictureInfoKHR::default() - .std_picture_info(stable(&self.pic_std)) - .slice_offsets(stable(&self.slice_offsets[..])); - - self.begin_info = vk::VideoBeginCodingInfoKHR::default() - .video_session(p.session) - .video_session_parameters(p.session_params) - .reference_slots(stable(&self.begin_slots[..n + 1])); - self.decode_info = vk::VideoDecodeInfoKHR::default() - .src_buffer(p.bitstream) - .src_buffer_offset(0) - .src_buffer_range(p.bitstream_range) - .dst_picture_resource(self.output_resource) - .setup_reference_slot(stable(&self.setup_slot)) - .reference_slots(stable(&self.decode_slots[..n])) - .push(stable_mut(&mut self.pic_info)); - } - } - - /// The `vkCmdBeginVideoCodingKHR` argument. Valid after [`Self::link`]. - pub(super) fn begin_info(&self) -> &vk::VideoBeginCodingInfoKHR<'_> { - &self.begin_info + fn dpb_slot(std: &'static StdVideoDecodeH264ReferenceInfo) -> Self::DpbSlot { + vk::VideoDecodeH264DpbSlotInfoKHR::default().std_reference_info(std) } - /// The `vkCmdDecodeVideoKHR` argument. Valid after [`Self::link`]. - pub(super) fn decode_info(&self) -> &vk::VideoDecodeInfoKHR<'_> { - &self.decode_info + fn picture_info( + picture: &'static mut StdVideoDecodeH264PictureInfo, + offsets: &'static [u32], + ) -> Self::PictureInfo { + vk::VideoDecodeH264PictureInfoKHR::default() + .std_picture_info(picture) + .slice_offsets(offsets) } } diff --git a/src/decoder/h264/session.rs b/src/decoder/h264/session.rs index 89a26919..53c8b50a 100644 --- a/src/decoder/h264/session.rs +++ b/src/decoder/h264/session.rs @@ -7,24 +7,30 @@ use ash::vk; use ash::vk::TaggedStructure; use tracing::debug; -use crate::decoder::common::{DecoderCommon, SessionPlan, query_decode_caps}; -use crate::decoder::frames::{DecodedPicture, ReorderBuffer}; +use crate::decoder::DecodeConfig; +use crate::decoder::annexb::START_CODE; +use crate::decoder::common::DecoderCommon; +use crate::decoder::driver::{CodecDecoder, Decoded}; +use crate::decoder::frames::DecodedPicture; use crate::decoder::h264::dpb::{DecodeDpb, PictureState}; use crate::decoder::h264::parser::{ self, NalType, NalUnit, Pps, SliceHeader, SliceType, Sps, iter_nal_units, }; -use crate::decoder::h264::record::{DecodeRecord, LinkParams}; +use crate::decoder::h264::record::H264Record; use crate::decoder::pipeline::DECODE_PIPELINE_DEPTH; -use crate::decoder::{DecodeConfig, DecodeStatus, Framing}; +use crate::decoder::plan::StreamNeeds; +use crate::decoder::profile::DecodeProfile; +use crate::decoder::record::{DecodeRecord, LinkParams}; use crate::encoder::{BitDepth, ChromaFormat}; use crate::error::{PixelForgeError, Result}; -use crate::video::{align_up, get_video_format, query_supported_video_formats}; use crate::vulkan::VideoContext; /// H.264 decoder. /// /// Owns only what is specific to H.264: the parameter sets seen so far, and the -/// POC / reference-marking state. Everything Vulkan lives in [`DecoderCommon`]. +/// POC / reference-marking state. Everything Vulkan lives in [`DecoderCommon`], +/// and the flow around a picture (framing, keyframes, reordering) is +/// [`StreamDecoder`](crate::decoder::driver::StreamDecoder)'s. pub(crate) struct H264Decoder { common: DecoderCommon, @@ -49,33 +55,12 @@ pub(crate) struct H264Decoder { /// What each pipeline slot's decode command was recorded from, indexed like /// the pipeline's slots. Never resized or moved out of: the records point /// into themselves. - records: Box<[DecodeRecord]>, + records: Box<[DecodeRecord]>, - /// Display-order reordering, which holds pictures by pinning their DPB - /// slots rather than by copying them out. - reorder: ReorderBuffer, /// `max_num_reorder_frames` for the active SPS: how deep the reorder buffer /// may hold pictures before emitting. Set by `activate`, which also sizes /// the DPB so that many pictures can be pinned rather than copied. reorder_depth: usize, - - /// How input is framed, which decides whether the end of a `decode` call is - /// also the end of a coded picture. - framing: Framing, - /// Bytes of a coded picture that has not been seen to end yet, held over - /// from earlier `decode` calls. Only ever non-empty in - /// [`Framing::ByteStream`]. - carry: Vec, - /// The `pts` of the call that delivered the first byte of `carry`, so a - /// picture is timestamped by when it started arriving rather than by which - /// call happened to complete it. - carry_pts: u64, - - /// Whether decoding is waiting for a keyframe (IDR) before it can produce - /// output. True until the first IDR is decoded: non-IDR pictures reference - /// state that does not exist yet, so they are skipped and the caller is told - /// to request an IDR. This is the normal state when joining a stream. - awaiting_keyframe: bool, } /// What `group_slices` did with a slice. @@ -88,7 +73,7 @@ enum Grouped { } /// The slices making up one picture, gathered from the input. -struct Picture<'a> { +pub(crate) struct Picture<'a> { header: SliceHeader, nal_type: NalType, ref_idc: u8, @@ -116,55 +101,17 @@ impl H264Decoder { active_sps: None, active_pps: None, dpb: None, + // SAFETY: the picture info is plain C data, for which all-zero is + // a valid (if meaningless) value. records: (0..DECODE_PIPELINE_DEPTH) - .map(|_| DecodeRecord::new()) + .map(|_| DecodeRecord::new(unsafe { std::mem::zeroed() })) .collect(), - reorder: ReorderBuffer::new(), reorder_depth: 0, - framing: config.framing, - carry: Vec::new(), - carry_pts: 0, - awaiting_keyframe: true, }) } - /// Emit any pictures still held for reordering. Call at end of stream. - /// - /// These pictures were copied out by earlier calls, so this submits no new - /// GPU work; the future still resolves in call order behind whatever is - /// already in flight. - pub(crate) fn finish(&mut self) -> Result<()> { - // In byte-stream framing the tail is a picture that was never seen to - // end because nothing followed it. End of stream is what ends it. - if !self.carry.is_empty() { - let tail = std::mem::take(&mut self.carry); - let pts = self.carry_pts; - // A tail that cannot be decoded is not worth failing over: the - // frames already decoded still have to come out. - self.decode_frames(&tail, pts)?; - } - let frames = self.reorder.flush(); - self.common.finish_stream(frames); - Ok(()) - } - - /// Hand over the frame channel's receiving end, once. - pub(crate) fn take_frame_receiver(&mut self) -> Option { - self.common.frames_rx.take() - } - - pub(crate) fn picture_format(&self) -> Option { - self.common.session.as_ref().map(|a| a.picture_format) - } - - /// Build the H.264 decode profile info for a parsed SPS. - fn profile_for( - sps: &Sps, - ) -> Result<( - vk::VideoDecodeH264ProfileInfoKHR<'static>, - ChromaFormat, - BitDepth, - )> { + /// Build the H.264 decode profile for a parsed SPS. + fn profile_for(sps: &Sps) -> Result<(DecodeProfile, ChromaFormat, BitDepth)> { let chroma_format = match sps.chroma_format_idc { 1 => ChromaFormat::Yuv420, 3 => ChromaFormat::Yuv444, @@ -207,10 +154,7 @@ impl H264Decoder { } }; - let profile = vk::VideoDecodeH264ProfileInfoKHR::default() - .std_profile_idc(std_profile) - .picture_layout(vk::VideoDecodeH264PictureLayoutFlagsKHR::PROGRESSIVE); - + let profile = DecodeProfile::h264(std_profile, chroma_format, bit_depth); Ok((profile, chroma_format, bit_depth)) } @@ -237,231 +181,38 @@ impl H264Decoder { )); } - let (mut profile, chroma_format, bit_depth) = Self::profile_for(sps)?; - let chroma_subsampling = match chroma_format { - ChromaFormat::Yuv420 => vk::VideoChromaSubsamplingFlagsKHR::TYPE_420, - ChromaFormat::Yuv444 => vk::VideoChromaSubsamplingFlagsKHR::TYPE_444, - other => { - return Err(PixelForgeError::InvalidInput(format!( - "H.264 decode: unsupported chroma format {:?}", - other - ))); - } - }; - let depth_flag = match bit_depth { - BitDepth::Eight => vk::VideoComponentBitDepthFlagsKHR::TYPE_8, - BitDepth::Ten => vk::VideoComponentBitDepthFlagsKHR::TYPE_10, - }; - let profile_info = vk::VideoProfileInfoKHR::default() - .video_codec_operation(vk::VideoCodecOperationFlagsKHR::DECODE_H264) - .chroma_subsampling(chroma_subsampling) - .luma_bit_depth(depth_flag) - .chroma_bit_depth(depth_flag) - .push(&mut profile); - - // Query capabilities for this exact profile. The chain borrows the - // codec caps structs, so scope it and copy the results out. - let mut h264_caps = vk::VideoDecodeH264CapabilitiesKHR::default(); - let mut decode_caps = vk::VideoDecodeCapabilitiesKHR::default(); - let ( - max_coded_extent, - picture_access_granularity, - max_dpb_slots, - max_active_reference_pictures, - cap_flags, - std_header_version, - min_bitstream_buffer_offset_alignment, - min_bitstream_buffer_size_alignment, - ) = { - let mut caps = vk::VideoCapabilitiesKHR::default() - .push(&mut h264_caps) - .push(&mut decode_caps); - query_decode_caps(&self.common.context, &profile_info, &mut caps)?; - ( - caps.max_coded_extent, - caps.picture_access_granularity, - caps.max_dpb_slots, - caps.max_active_reference_pictures, - caps.flags, - caps.std_header_version, - caps.min_bitstream_buffer_offset_alignment, - caps.min_bitstream_buffer_size_alignment, - ) - }; - let decode_flags = decode_caps.flags; - - let coded_width = align_up(sps.coded_width(), picture_access_granularity.width.max(1)); - let coded_height = align_up(sps.coded_height(), picture_access_granularity.height.max(1)); - if coded_width > max_coded_extent.width || coded_height > max_coded_extent.height { - return Err(PixelForgeError::InvalidInput(format!( - "H.264 decode: stream is {}x{}, device maximum is {}x{}", - coded_width, coded_height, max_coded_extent.width, max_coded_extent.height - ))); - } - - self.common.bitstream_offset_alignment = min_bitstream_buffer_offset_alignment.max(1); - self.common.bitstream_size_alignment = min_bitstream_buffer_size_alignment.max(1); - - let coincide = - decode_flags.contains(vk::VideoDecodeCapabilityFlagsKHR::DPB_AND_OUTPUT_COINCIDE); - debug!("H.264 decode capability flags: {:?}", decode_flags); - let use_layered_dpb = - !cap_flags.contains(vk::VideoCapabilityFlagsKHR::SEPARATE_REFERENCE_IMAGES); + let (profile, chroma_format, bit_depth) = Self::profile_for(sps)?; // The stream needs max_num_ref_frames references plus the current - // picture. On top of that come the spare slots, which is what lets a - // picture stay pinned in place past the decode that produced it instead - // of being copied out: `reorder_depth` of them for pictures waiting - // their turn in display order, and `output_depth` for frames the caller - // is holding. The device caps bound the total. - let required = (sps.max_num_ref_frames as u32 + 1).max(2); - let slot_limit = max_dpb_slots.min(crate::decoder::h264::dpb::MAX_DPB_SLOTS as u32); - if required > slot_limit { - return Err(PixelForgeError::InvalidInput(format!( - "H.264 decode: stream needs {} DPB slots, device supports {}", - required, max_dpb_slots - ))); - } - // Reorder depth comes from the stream (VUI); without it, bound by the - // reference count, which is never smaller than the reorder depth in - // practice. + // picture. Reorder depth comes from the stream (VUI); without it, bound + // by the reference count, which is never smaller than the reorder depth + // in practice. let reorder_depth = sps .max_num_reorder_frames .map(|v| v as usize) .unwrap_or(sps.max_num_ref_frames as usize); - // Spare slots only buy something when pictures can be pinned. Without - // that they are handed out as copies, which needs no DPB slot at all, - // so asking for spares would just reserve memory nothing uses. - let pinnable = coincide && self.common.context.has_unified_image_layouts(); - let wanted_spare = if pinnable { - reorder_depth + self.output_depth - } else { - 0 - }; - let slot_count = (required + wanted_spare as u32).min(slot_limit) as usize; - let spare_slots = slot_count - required as usize; - if spare_slots < wanted_spare { - debug!( - "H.264 decode: {} of {} spare DPB slots available \ - (reorder depth {} plus {} output slots; the stream's references \ - need {} of the device's {}); pictures beyond that are copied", - spare_slots, - wanted_spare, - reorder_depth, - self.output_depth, - required, - max_dpb_slots - ); - } - - // Only the reference pictures are ever active at once: the current - // picture and the output reservation are not references. - let max_active_references = (required - 1).min(max_active_reference_pictures); - if max_active_references < required - 1 { - return Err(PixelForgeError::InvalidInput(format!( - "H.264 decode: stream needs {} active reference pictures, device supports {}", - required - 1, - max_active_reference_pictures - ))); - } - - // Pick a picture format the device actually supports for decode. - // - // Ask for SAMPLED as well: a coincident DPB image is what the caller - // receives, and SAMPLED is what lets them read it in a shader instead - // of copying it out first. Fall back to the plain usage if the device - // reports no sampleable format, which costs the caller a copy but keeps - // decoding working. With a distinct output image nothing asks, since - // those pictures reach the caller as pool copies, which are sampleable - // whatever the device says about video images. - let base_usage = if coincide { - vk::ImageUsageFlags::VIDEO_DECODE_DPB_KHR - | vk::ImageUsageFlags::VIDEO_DECODE_DST_KHR - | vk::ImageUsageFlags::TRANSFER_SRC - } else { - vk::ImageUsageFlags::VIDEO_DECODE_DPB_KHR - }; - let sampled_usage = base_usage | vk::ImageUsageFlags::SAMPLED; - let sampled_formats = if coincide { - query_supported_video_formats(&self.common.context, &profile_info, sampled_usage) - .unwrap_or_default() - } else { - Vec::new() - }; - let (dpb_usage, sampleable, supported) = if sampled_formats.is_empty() { - let formats = - query_supported_video_formats(&self.common.context, &profile_info, base_usage)?; - (base_usage, false, formats) - } else { - (sampled_usage, true, sampled_formats) - }; - let preferred = get_video_format(chroma_format, bit_depth); - let chosen = supported - .iter() - .find(|f| f.format == preferred) - .or_else(|| supported.first()) - .copied() - .ok_or_else(|| { - PixelForgeError::NoSuitableDevice( - "H.264 decode: no supported picture format for this profile".to_string(), - ) - })?; - let picture_format = chosen.format; - - // MUTABLE_FORMAT lets a consumer view the picture's planes separately, - // which is the only way to read a multi-planar image without a - // sampler-YCbCr conversion, and the only way at all for a renderer - // whose shaders come from a toolchain that cannot express one (naga and - // therefore wgpu, among others). It is opportunistic: the driver - // enumerates which creation flags it accepts for this profile, format - // and usage, and DPB-only images on both RADV and ANV accept none. - // - // Naming the plane formats keeps a driver from having to assume the - // image might be reinterpreted as anything, which is what would - // otherwise cost it compression. - let plane_views = chosen - .create_flags - .contains(vk::ImageCreateFlags::MUTABLE_FORMAT); - let image_flags = if plane_views { - vk::ImageCreateFlags::MUTABLE_FORMAT - } else { - vk::ImageCreateFlags::empty() - }; - let view_formats: Vec = if plane_views { - let mut list = vec![picture_format]; - list.extend(crate::video::plane_view_formats(picture_format)); - list - } else { - Vec::new() - }; - - // Hand the shared layer everything it needs to build the session; it - // owns the Vulkan objects, this module owns only the H.264 reasoning - // that produced these numbers. - let plan = SessionPlan { - coded_width, - coded_height, - picture_format, - bit_depth, + let needs = StreamNeeds { + codec: "H.264", + width: sps.coded_width(), + height: sps.coded_height(), chroma_format, - slot_count, - spare_slots, - max_active_references, - coincide, - pinnable, - sampleable, - plane_views, - image_flags, - view_formats, - use_layered_dpb, - dpb_usage, + bit_depth, + references: sps.max_num_ref_frames as u32, + active_references: sps.max_num_ref_frames as u32, + slot_limit: crate::decoder::h264::dpb::MAX_DPB_SLOTS as u32, + reorder_depth, + output_depth: self.output_depth, + film_grain: false, }; - self.common.create_session( - &plan, - &profile_info, - &std_header_version, - |common, session| Self::build_session_params(common, session, sps, pps), - )?; + let mut h264_caps = vk::VideoDecodeH264CapabilitiesKHR::default(); + let plan = self + .common + .plan_session(profile.info(), &mut h264_caps, &needs)?; + let slot_count = plan.slot_count; + self.common + .create_session(&plan, profile, |common, session| { + Self::build_session_params(common, session, sps, pps) + })?; self.active_sps = Some(sps.clone()); self.active_pps = Some(pps.clone()); @@ -469,23 +220,6 @@ impl H264Decoder { let pins = self.common.session()?.slot_pins.clone(); self.dpb = Some(DecodeDpb::new(slot_count, pins)); - debug!( - "H.264 decode session: {}x{} {:?}, {} DPB slots ({} spare), \ - layered={}, coincide={}, pinnable={}, sampleable={}, \ - plane_views={}, bitstream alignment {}/{} (offset/size)", - coded_width, - coded_height, - picture_format, - slot_count, - spare_slots, - use_layered_dpb, - coincide, - pinnable, - sampleable, - plane_views, - self.common.bitstream_offset_alignment, - self.common.bitstream_size_alignment - ); Ok(()) } @@ -647,119 +381,8 @@ impl H264Decoder { Ok((pictures, awaiting)) } - /// Decode a chunk of input, doing the framing the configuration calls for. - pub(crate) fn decode(&mut self, data: &[u8], pts: u64) -> Result { - match self.framing { - // The caller guarantees the chunk ends on a picture boundary, so it - // can go straight to the decoder with no buffering and no copy. - Framing::FrameAligned => self.decode_frames(data, pts), - Framing::ByteStream => self.decode_byte_stream(data, pts), - } - } - - /// Framing for input that may cut anywhere. - /// - /// A coded picture is only known to have ended once the next one starts, so - /// everything up to the last picture start is complete and decodable, and - /// everything from it onward is held over for the next call. `flush` ends - /// the final picture. - fn decode_byte_stream(&mut self, data: &[u8], pts: u64) -> Result { - let carried = self.carry.len(); - let mut buffer = std::mem::take(&mut self.carry); - buffer.extend_from_slice(data); - - // Nothing before the last picture start can still be waiting for more - // slices. With no picture start at all, nothing is complete yet. - let split = super::complete_prefix(&buffer); - - let result = if split > 0 { - self.decode_frames(&buffer[..split], self.carry_pts) - } else { - // Not enough bytes to complete a picture yet. - Ok(DecodeStatus::Buffered) - }; - - // The held-over picture is timestamped by the call its first byte came - // in on, which is this one only if the split consumed everything the - // previous calls had left. - if split >= carried { - self.carry_pts = pts; - } - buffer.drain(..split); - self.carry = buffer; - result - } - - /// Decode input already known to end on a picture boundary. - /// - /// Frames go to the consumer as each picture's work is handed to the - /// completion thread, so a call decoding many pictures does not hold their - /// frames until it returns. - fn decode_frames(&mut self, data: &[u8], pts: u64) -> Result { - // What is missing when a picture cannot be decoded yet; set only while - // no frame has been produced, so a keyframe later in the same buffer - // still yields output. Seeded with slices skipped during splitting for - // missing parameter sets. - let (pictures, mut awaiting) = self.split_pictures(data)?; - // Whether any picture actually reached the GPU, which decides between - // returning a batch and asking for a keyframe. - let mut decoded_any = false; - - for picture in pictures { - let is_idr = picture.nal_type == NalType::IdrSlice; - - // A non-IDR picture before the first keyframe references reference - // pictures and POC state that do not exist yet. Skip it and ask for - // a keyframe rather than decoding garbage. - if self.awaiting_keyframe && !is_idr { - awaiting.get_or_insert_with(|| { - "no keyframe decoded yet; non-IDR picture cannot be decoded".to_string() - }); - continue; - } - - // The parameter sets this picture was grouped under, not whatever - // currently sits at those ids: a later SPS in the same chunk must - // not reach back and change how this picture is decoded. - let (sps, pps) = (picture.sps.clone(), picture.pps.clone()); - - // (Re)build the session if the stream's geometry or ids changed. - self.activate(&sps, &pps)?; - - if let Some(frame) = self.decode_picture(&picture, &sps, pts)? { - // A decoded IDR re-establishes a valid decode point. - if is_idr { - self.awaiting_keyframe = false; - } - let depth_reorder = self.reorder_depth; - let ready = self.reorder.push(&mut self.common, &frame, depth_reorder)?; - decoded_any = true; - // The picture's submissions are complete as far as recording - // goes; hand its slot and frames to the completion thread and - // move on to the next slot. - self.common.end_picture(ready); - } - } - - if decoded_any { - return Ok(DecodeStatus::Decoded); - } - // Only ask for a keyframe when nothing was decoded: if one arrived - // later in the same buffer, its pictures take priority. - if let Some(reason) = awaiting { - debug!("H.264 decode: awaiting keyframe: {}", reason); - return Ok(DecodeStatus::NeedsKeyframe); - } - Ok(DecodeStatus::Buffered) - } - - /// Record and submit the decode of a single picture, and wait for it. - fn decode_picture( - &mut self, - picture: &Picture, - sps: &Sps, - pts: u64, - ) -> Result> { + /// Record and submit the decode of a single picture, without waiting for it. + fn decode_picture(&mut self, picture: &Picture, sps: &Sps, pts: u64) -> Result { // --- Host-side bookkeeping: POC, slot, reference set --- let state = { let dpb = self.dpb.as_mut().expect("activated above"); @@ -796,82 +419,24 @@ impl H264Decoder { // completed. Staging before this waits overwrote coded data the GPU was // still reading. self.common.begin_decode_commands()?; - let (buffer_range, slice_offsets) = self.stage_slices(picture, sps)?; + let (buffer_range, slice_offsets) = + self.common.stage_slices(&picture.slices, &START_CODE)?; self.common.record_barriers(slot); self.record_decode(picture, &state, slot, buffer_range, &slice_offsets)?; self.common.submit_decode()?; // --- Publish the frame, then retire references --- - let active = self.common.session.as_mut().expect("active"); - active.dpb_slot_active[slot as usize] = true; - - let picture_layout = self.common.picture_layout(); - let output_layout = self.common.output_layout(); - let generation = self.common.generation; - let active = self.common.session.as_mut().expect("active"); - let (image, image_view, layout, array_layer) = if active.coincide { - let (image, layer) = active.dpb_image_for_slot(slot); - (image, active.dpb_entry(slot).2, picture_layout, layer) - } else { - let (image, _, view) = active - .output_image - .expect("non-coincide implies output image"); - (image, view, output_layout, 0) - }; - let (width, height) = sps.display_dimensions(); - let frame = DecodedPicture { - slot, - image, - image_view, - layout, - array_layer, - chroma_format: active.chroma_format, - bit_depth: active.bit_depth, - generation, - width, - height, - coded_width: active.coded_width, - coded_height: active.coded_height, - pts, - display_order: state.poc, - is_keyframe: state.is_idr, - }; + let frame = self + .common + .decoded_picture(slot, width, height, pts, state.poc, state.is_idr); self.dpb .as_mut() .expect("active") .end_picture(slot, &state, sps); - Ok(Some(frame)) - } - - /// Copy the picture's slice NALs into the staging buffer. - /// - /// Returns the aligned buffer range to hand to the driver and the offset of - /// each slice within it. - /// Stage this picture's slices for the driver. - /// - /// The copying is generic; only the profile (needed to size the buffer) is - /// H.264's business. - fn stage_slices(&mut self, picture: &Picture, sps: &Sps) -> Result<(u64, Vec)> { - let (mut profile, chroma_format, bit_depth) = Self::profile_for(sps)?; - let chroma_subsampling = match chroma_format { - ChromaFormat::Yuv420 => vk::VideoChromaSubsamplingFlagsKHR::TYPE_420, - _ => vk::VideoChromaSubsamplingFlagsKHR::TYPE_444, - }; - let depth_flag = match bit_depth { - BitDepth::Eight => vk::VideoComponentBitDepthFlagsKHR::TYPE_8, - BitDepth::Ten => vk::VideoComponentBitDepthFlagsKHR::TYPE_10, - }; - let profile_info = vk::VideoProfileInfoKHR::default() - .video_codec_operation(vk::VideoCodecOperationFlagsKHR::DECODE_H264) - .chroma_subsampling(chroma_subsampling) - .luma_bit_depth(depth_flag) - .chroma_bit_depth(depth_flag) - .push(&mut profile); - - self.common.stage_slices(&picture.slices, &profile_info) + Ok(frame) } /// Record `vkCmdBeginVideoCoding` / `vkCmdDecodeVideo` / `vkCmdEndVideoCoding`. @@ -884,7 +449,6 @@ impl H264Decoder { slice_offsets: &[u32], ) -> Result<()> { let active = self.common.session.as_ref().expect("active"); - let command_buffer = self.common.decode_command_buffer(); // The slot's previous decode has finished (`begin_decode_commands` // waited for it), so its record can be rewritten. @@ -910,6 +474,7 @@ impl H264Decoder { record.push_reference( r.slot, active.dpb_entry(r.slot).2, + None, ash::vk::native::StdVideoDecodeH264ReferenceInfo { flags, FrameNum: frame_num, @@ -952,6 +517,8 @@ impl H264Decoder { PicOrderCnt: [state.poc, state.poc], }; + *record.picture_mut() = pic_std; + // The decode destination is the DPB slot itself when the implementation // supports coincide, otherwise the separate output image. record.link(&LinkParams { @@ -967,43 +534,51 @@ impl H264Decoder { setup_view: active.dpb_entry(slot).2, output_view: active.output_image.map(|(_, _, view)| view), setup_std, - pic_std, }); - unsafe { - self.common - .video_queue_fn - .cmd_begin_video_coding(command_buffer, record.begin_info()); - } - - // On the first use of the session, all DPB slots must be reset. - if !active.dpb_slot_active.iter().any(|&a| a) { - let control = vk::VideoCodingControlInfoKHR::default() - .flags(vk::VideoCodingControlFlagsKHR::RESET); - unsafe { - self.common - .video_queue_fn - .cmd_control_video_coding(command_buffer, &control); - } - } - - unsafe { - self.common - .video_decode_fn - .cmd_decode_video(command_buffer, record.decode_info()); - self.common - .video_queue_fn - .cmd_end_video_coding(command_buffer, &vk::VideoEndCodingInfoKHR::default()); - } + self.common.record_coding(record); Ok(()) } } -impl Drop for H264Decoder { - fn drop(&mut self) { - // The reorder pool's images must be freed while `common` (and its - // device) is still alive; `common`'s own Drop runs afterward. - self.reorder.destroy(&self.common); +impl CodecDecoder for H264Decoder { + type Unit<'a> = Picture<'a>; + + const NAME: &'static str = "H.264"; + + fn common(&self) -> &DecoderCommon { + &self.common + } + + fn common_mut(&mut self) -> &mut DecoderCommon { + &mut self.common + } + + fn complete_prefix(buffer: &[u8]) -> usize { + super::complete_prefix(buffer) + } + + fn split_units<'a>(&mut self, data: &'a [u8]) -> Result<(Vec>, Option)> { + self.split_pictures(data) + } + + fn is_keyframe(picture: &Picture<'_>) -> bool { + picture.nal_type == NalType::IdrSlice + } + + fn decode_unit(&mut self, picture: &Picture<'_>, pts: u64) -> Result { + // The parameter sets this picture was grouped under, not whatever + // currently sits at those ids: a later SPS in the same chunk must not + // reach back and change how this picture is decoded. + let (sps, pps) = (picture.sps.clone(), picture.pps.clone()); + + // (Re)build the session if the stream's geometry or ids changed. + self.activate(&sps, &pps)?; + self.decode_picture(picture, &sps, pts).map(Decoded::Shown) + } + + fn reorder_depth(&self) -> usize { + self.reorder_depth } } diff --git a/src/decoder/h265/dpb.rs b/src/decoder/h265/dpb.rs new file mode 100644 index 00000000..c5440965 --- /dev/null +++ b/src/decoder/h265/dpb.rs @@ -0,0 +1,440 @@ +//! Decode-side H.265 picture order count and reference picture set handling. +//! +//! H.265 has no sliding window and no marking commands: every slice header +//! names the complete set of pictures to keep (the reference picture set), and +//! anything not named is dropped *before* the current picture is decoded. This +//! module derives the picture order count (clause 8.3.1), resolves the +//! reference picture set against the pictures held (clause 8.3.2), and keeps +//! the DPB slots the way the H.264 decoder's `DecodeDpb` does, so the session +//! code can drive both alike. +//! +//! Random access follows `NoRaslOutputFlag`: an IDR or BLA picture, the first +//! picture of the stream and the first after an end of sequence all start a +//! new coded video sequence, and the RASL pictures that lead such a CRA are +//! skipped, since they reference pictures the decoder never saw. +//! `HandleCraAsBlaFlag` is always 0, and unavailable reference pictures +//! (clause 8.3.3) are never generated: the only pictures that would need them +//! are those RASL pictures, and they are not decoded. + +use std::sync::Arc; + +use super::parser::{NalHeader, NalType, invalid}; +use super::slice::SliceHeader; +use super::sps::Sps; +use crate::decoder::frames::SlotPins; +use crate::error::Result; + +/// DPB slots this module can address: `MaxDpbSize` (16) pictures, which +/// includes the current one, plus one so a picture held for output never +/// blocks the next decode. +pub(crate) const MAX_DPB_SLOTS: usize = 17; + +/// Entries in each of Vulkan's `RefPicSet*` arrays +/// (`STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE`). +pub(crate) const VULKAN_REF_PIC_SET_LIST_SIZE: usize = 8; + +/// The value Vulkan's `RefPicSet*` arrays use for "no reference picture" +/// (`STD_VIDEO_H265_NO_REFERENCE_PICTURE`). +pub(crate) const NO_REFERENCE_PICTURE: u8 = 0xFF; + +/// A picture currently held in the DPB as a reference. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct RefPicture { + /// DPB slot index this picture lives in. + pub slot: u8, + /// `PicOrderCntVal`. + pub poc: i32, + /// Marked "used for long-term reference"; otherwise short-term. + pub long_term: bool, +} + +/// One entry of a reference picture set list: the POC the slice header asks +/// for, and the slot of the picture that has it. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct RpsEntry { + pub poc: i32, + /// `None` is the standard's "no reference picture". + pub slot: Option, +} + +/// The five lists of clause 8.3.2, resolved to DPB slots. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct RefPicSet { + /// `RefPicSetStCurrBefore`: short-term, used by the current picture, + /// preceding it in output order. + pub st_curr_before: Vec, + /// `RefPicSetStCurrAfter`: short-term, used, following in output order. + pub st_curr_after: Vec, + /// `RefPicSetStFoll`: short-term, kept for later pictures only. + pub st_foll: Vec, + /// `RefPicSetLtCurr`: long-term, used by the current picture. + pub lt_curr: Vec, + /// `RefPicSetLtFoll`: long-term, kept for later pictures only. + pub lt_foll: Vec, +} + +impl RefPicSet { + /// Whether a picture the current one predicts from is not in the DPB. + /// + /// Missing entries in the two `Foll` lists do not matter to the current + /// picture and are not counted (the standard ignores them too). + pub fn missing_current(&self) -> bool { + self.st_curr_before + .iter() + .chain(&self.st_curr_after) + .chain(&self.lt_curr) + .any(|e| e.slot.is_none()) + } + + /// A list in the form Vulkan's `RefPicSetStCurrBefore`, `RefPicSetStCurrAfter` + /// and `RefPicSetLtCurr` take: slot indices, padded with + /// [`NO_REFERENCE_PICTURE`]. + pub fn vulkan_list(list: &[RpsEntry]) -> Result<[u8; VULKAN_REF_PIC_SET_LIST_SIZE]> { + if list.len() > VULKAN_REF_PIC_SET_LIST_SIZE { + return Err(invalid( + "more than 8 pictures in one reference picture set list", + )); + } + let mut out = [NO_REFERENCE_PICTURE; VULKAN_REF_PIC_SET_LIST_SIZE]; + for (dst, entry) in out.iter_mut().zip(list) { + *dst = entry.slot.unwrap_or(NO_REFERENCE_PICTURE); + } + Ok(out) + } +} + +/// Why a picture is not to be decoded. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SkipReason { + /// No IRAP picture has started a coded video sequence yet (the stream + /// starts mid-sequence, or after an end of sequence). The caller should + /// ask for a keyframe. + AwaitingIrap, + /// A RASL picture led by a CRA that started decoding: it references + /// pictures from before the CRA, which the decoder never had. Skipping it + /// is normal and not an error. + RaslWithoutReferences, +} + +/// Per-picture state derived from the bitstream. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct PictureState { + /// `PicOrderCntVal`. + pub poc: i32, + pub nal_type: NalType, + pub temporal_id: u8, + /// `NoRaslOutputFlag`. Meaningful for IRAP pictures only. + pub no_rasl_output_flag: bool, + /// An IRAP picture with `NoRaslOutputFlag`: decoding can start here, and + /// nothing after it in decoding order references anything before it. + pub is_keyframe: bool, + /// `PicOutputFlag`: whether the picture is to be output at all. + pub pic_output_flag: bool, + /// `NoOutputOfPriorPicsFlag` (clause C.5.2.2), set only on a keyframe. + /// + /// On a keyframe every picture still waiting for output is either output + /// first (false) or discarded (true). It is inferred to be true for a CRA, + /// so a caller that flushes its output at an end of sequence NAL unit, + /// before the CRA that follows it arrives, loses nothing. + pub no_output_of_prior_pics: bool, + /// Whether the decoded picture is kept as a reference afterwards. False + /// only for a sub-layer non-reference picture of the highest sub-layer, + /// which nothing can reference. + pub is_reference: bool, + /// The reference picture set, resolved to slots. Empty for IDR pictures. + pub rps: RefPicSet, + /// Whether a picture the current one predicts from is missing (see + /// [`RefPicSet::missing_current`]). Decoding it anyway produces corrupt + /// output; the caller would normally skip it and ask for a keyframe. + pub missing_reference: bool, +} + +/// What [`DecodeDpb::begin_picture`] decided. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PictureDecision { + Decode(PictureState), + Skip(SkipReason), +} + +/// Decode-side DPB: POC state, the reference pictures held and their slots. +pub(crate) struct DecodeDpb { + /// Slots reserved by decoded frames the caller still holds. Never handed + /// out for a new picture, however the reference rules mark them. + pins: Arc, + /// Reference pictures, in decoding order. + refs: Vec, + /// Which slots are occupied (by a reference or the in-flight picture). + slot_used: [bool; MAX_DPB_SLOTS], + /// Number of slots the session was created with. + slot_count: usize, + /// The next picture is the first of the stream or follows an end of + /// sequence, so only an IRAP can start decoding. + at_sequence_start: bool, + /// `NoRaslOutputFlag` of the last IRAP, which decides whether the RASL + /// pictures associated with it are decodable. + irap_no_rasl_output: bool, + /// `PicOrderCntVal` of `prevTid0Pic` (clause 8.3.1). + prev_tid0_poc: i32, +} + +impl DecodeDpb { + pub fn new(slot_count: usize, pins: Arc) -> Self { + Self { + pins, + refs: Vec::new(), + slot_used: [false; MAX_DPB_SLOTS], + slot_count: slot_count.min(MAX_DPB_SLOTS), + at_sequence_start: true, + irap_no_rasl_output: true, + prev_tid0_poc: 0, + } + } + + /// Current reference pictures, in the order they were decoded. + /// + /// After [`Self::begin_picture`] these are exactly the pictures in the + /// current reference picture set, `Foll` lists included; all of them must + /// stay active in the Vulkan session for later pictures. + pub fn references(&self) -> &[RefPicture] { + &self.refs + } + + /// Note an end of sequence NAL unit: the next picture must be an IRAP, and + /// starts a new coded video sequence with `NoRaslOutputFlag` = 1. + /// + /// Also the way to resynchronise after an error: the next CRA is then + /// treated as a random access point and its RASL pictures are skipped. + pub fn end_of_sequence(&mut self) { + self.at_sequence_start = true; + } + + /// Derive POC and the reference picture set for the picture about to be + /// decoded, and drop every reference the set does not name. + /// + /// `slice` is the header of the picture's first slice segment. Must be + /// called once per picture, before [`Self::try_allocate_slot`]; a skipped + /// picture changes no state, and needs no slot. + pub fn begin_picture( + &mut self, + nal: &NalHeader, + slice: &SliceHeader, + sps: &Sps, + ) -> Result { + let nal_type = nal.nal_type; + if !nal_type.is_vcl() || slice.dependent_slice_segment_flag { + return Err(invalid("begin_picture needs an independent slice segment")); + } + + let irap = nal_type.is_irap(); + if !irap && self.at_sequence_start { + return Ok(PictureDecision::Skip(SkipReason::AwaitingIrap)); + } + // HandleCraAsBlaFlag is 0, so a CRA starts a new sequence only where + // it is the first picture (clause 8.1.3). + let no_rasl_output = + irap && (nal_type.is_idr() || nal_type.is_bla() || self.at_sequence_start); + if irap { + self.irap_no_rasl_output = no_rasl_output; + } + if nal_type.is_rasl() && self.irap_no_rasl_output { + return Ok(PictureDecision::Skip(SkipReason::RaslWithoutReferences)); + } + self.at_sequence_start = false; + + let poc = self.derive_poc(nal, slice, sps, irap && no_rasl_output); + + if irap && no_rasl_output { + // A new coded video sequence: nothing before it is referenced. + self.drop_refs(|_| true); + } + let rps = if nal_type.is_idr() { + RefPicSet::default() + } else { + self.apply_rps(poc, slice, sps)? + }; + let missing_reference = rps.missing_current(); + + let keyframe = irap && no_rasl_output; + Ok(PictureDecision::Decode(PictureState { + poc, + nal_type, + temporal_id: nal.temporal_id, + no_rasl_output_flag: no_rasl_output, + is_keyframe: keyframe, + pic_output_flag: slice.pic_output_flag, + no_output_of_prior_pics: keyframe + && (nal_type == NalType::Cra || slice.no_output_of_prior_pics_flag), + is_reference: !(nal_type.is_sub_layer_non_reference() + && nal.temporal_id as usize >= sps.highest_tid()), + rps, + missing_reference, + })) + } + + /// Clause 8.3.1. Updates `prevTid0Pic` when the current picture qualifies. + fn derive_poc( + &mut self, + nal: &NalHeader, + slice: &SliceHeader, + sps: &Sps, + new_sequence: bool, + ) -> i32 { + let max_lsb = sps.max_pic_order_cnt_lsb() as i32; + let lsb = slice.slice_pic_order_cnt_lsb as i32; + let msb = if new_sequence { + 0 + } else { + let prev_lsb = self.prev_tid0_poc & (max_lsb - 1); + let prev_msb = self.prev_tid0_poc - prev_lsb; + if lsb < prev_lsb && prev_lsb - lsb >= max_lsb / 2 { + prev_msb + max_lsb + } else if lsb > prev_lsb && lsb - prev_lsb > max_lsb / 2 { + prev_msb - max_lsb + } else { + prev_msb + } + }; + let poc = msb + lsb; + + // prevTid0Pic: TemporalId 0 and not a RASL, RADL or sub-layer + // non-reference picture. + let t = nal.nal_type; + if nal.temporal_id == 0 && !t.is_rasl() && !t.is_radl() && !t.is_sub_layer_non_reference() { + self.prev_tid0_poc = poc; + } + poc + } + + /// Clause 8.3.2 for a non-IDR picture: build the five lists, mark the + /// long-term pictures, and drop every reference left out. + fn apply_rps(&mut self, poc: i32, slice: &SliceHeader, sps: &Sps) -> Result { + let st = &slice + .short_term_ref_pic_set(sps) + .ok_or_else(|| invalid("non-IDR picture without a short-term reference picture set"))? + .rps; + let max_lsb = sps.max_pic_order_cnt_lsb() as i32; + let mut keep = vec![false; self.refs.len()]; + let mut set = RefPicSet::default(); + + // Long-term pictures first: they may be found among the short-term + // ones, and are then no longer candidates for the short-term lists. + for lt in &slice.long_term_refs { + let mut poc_lt = lt.poc_lsb_lt as i32; + if lt.delta_poc_msb_present_flag { + poc_lt += poc + - lt.delta_poc_msb_cycle_lt_accumulated as i32 * max_lsb + - (poc & (max_lsb - 1)); + } + let found = self.refs.iter().position(|r| { + if lt.delta_poc_msb_present_flag { + r.poc == poc_lt + } else { + r.poc & (max_lsb - 1) == poc_lt + } + }); + let slot = found.map(|i| { + keep[i] = true; + self.refs[i].long_term = true; + self.refs[i].slot + }); + let entry = RpsEntry { poc: poc_lt, slot }; + if lt.used_by_curr_pic_lt { + set.lt_curr.push(entry); + } else { + set.lt_foll.push(entry); + } + } + + let short_term = |target: i32, keep: &mut [bool]| { + let found = self + .refs + .iter() + .position(|r| !r.long_term && r.poc == target); + let slot = found.map(|i| { + keep[i] = true; + self.refs[i].slot + }); + RpsEntry { poc: target, slot } + }; + for (delta, used) in st.delta_poc_s0.iter().zip(&st.used_s0) { + let entry = short_term(poc + delta, &mut keep); + if *used { + set.st_curr_before.push(entry); + } else { + set.st_foll.push(entry); + } + } + for (delta, used) in st.delta_poc_s1.iter().zip(&st.used_s1) { + let entry = short_term(poc + delta, &mut keep); + if *used { + set.st_curr_after.push(entry); + } else { + set.st_foll.push(entry); + } + } + + let mut index = 0; + self.drop_refs(|_| { + let drop_it = !keep[index]; + index += 1; + drop_it + }); + Ok(set) + } + + /// Drop every reference matching `pred`, freeing its slot. + fn drop_refs(&mut self, mut pred: impl FnMut(&RefPicture) -> bool) { + let slot_used = &mut self.slot_used; + self.refs.retain(|r| { + let drop_it = pred(r); + if drop_it { + slot_used[r.slot as usize] = false; + } + !drop_it + }); + } + + /// Reserve a DPB slot for the current picture, if one is available. + /// + /// Slots pinned by a handed-out frame are skipped: the caller is still + /// reading them. `None` means every slot is either a reference or pinned. + pub fn try_allocate_slot(&mut self) -> Option { + for slot in 0..self.slot_count { + if !self.slot_used[slot] && !self.pins.is_pinned(slot as u8) { + self.slot_used[slot] = true; + return Some(slot as u8); + } + } + None + } + + /// Whether any slot is held by a frame the caller has not dropped, which + /// means waiting could free one. + pub fn has_pinned_slots(&self) -> bool { + self.pins.any_pinned() + } + + /// Release a slot allocated for a picture that will not be kept. + pub fn release_slot(&mut self, slot: u8) { + self.slot_used[slot as usize] = false; + } + + /// Record the just-decoded picture. It becomes a short-term reference + /// (clause 8.3.2, last paragraph) unless nothing can ever reference it, in + /// which case its slot is released. + pub fn end_picture(&mut self, slot: u8, state: &PictureState) { + if !state.is_reference { + self.release_slot(slot); + return; + } + self.refs.push(RefPicture { + slot, + poc: state.poc, + long_term: false, + }); + } +} + +#[cfg(test)] +#[path = "dpb_tests.rs"] +mod tests; diff --git a/src/decoder/h265/dpb_tests.rs b/src/decoder/h265/dpb_tests.rs new file mode 100644 index 00000000..f330e665 --- /dev/null +++ b/src/decoder/h265/dpb_tests.rs @@ -0,0 +1,665 @@ +//! DPB tests on synthetic slice headers, for the cases the fixtures do not +//! reach: POC wrap-around with `prevTid0Pic`, long-term pictures, missing +//! references, BLA and end of sequence. + +use super::*; +use crate::decoder::h265::parser::DecPicBufMgr; +use crate::decoder::h265::rps::{StRefPicSet, StRps}; +use crate::decoder::h265::slice::LongTermRef; + +/// MaxPicOrderCntLsb = 16, a 6-picture DPB, `sub_layers` temporal layers. +fn sps(sub_layers: u8) -> Sps { + let mgr = DecPicBufMgr { + max_dec_pic_buffering_minus1: [5; 7], + ..Default::default() + }; + Sps { + log2_max_pic_order_cnt_lsb_minus4: 0, + sps_max_sub_layers_minus1: sub_layers - 1, + dec_pic_buf_mgr: mgr, + ..Default::default() + } +} + +fn nal(nal_type: NalType, temporal_id: u8) -> NalHeader { + NalHeader { + nal_type, + layer_id: 0, + temporal_id, + } +} + +/// A slice header with an inline short-term set of (delta, used) pairs. +fn slice(lsb: u32, s0: &[(i32, bool)], s1: &[(i32, bool)]) -> SliceHeader { + SliceHeader { + first_slice_segment_in_pic_flag: true, + pic_output_flag: true, + slice_pic_order_cnt_lsb: lsb, + st_ref_pic_set: Some(StRefPicSet { + rps: StRps { + delta_poc_s0: s0.iter().map(|e| e.0).collect(), + used_s0: s0.iter().map(|e| e.1).collect(), + delta_poc_s1: s1.iter().map(|e| e.0).collect(), + used_s1: s1.iter().map(|e| e.1).collect(), + }, + ..Default::default() + }), + ..Default::default() + } +} + +/// Begin a picture that must be decoded, allocate its slot and finish it. +fn decode( + dpb: &mut DecodeDpb, + nal: NalHeader, + slice: &SliceHeader, + sps: &Sps, +) -> (PictureState, u8) { + let state = match dpb.begin_picture(&nal, slice, sps).unwrap() { + PictureDecision::Decode(state) => state, + PictureDecision::Skip(reason) => panic!("unexpected skip: {reason:?}"), + }; + let slot = dpb.try_allocate_slot().expect("free slot"); + dpb.end_picture(slot, &state); + (state, slot) +} + +fn skip_reason(dpb: &mut DecodeDpb, nal: NalHeader, slice: &SliceHeader, sps: &Sps) -> SkipReason { + match dpb.begin_picture(&nal, slice, sps).unwrap() { + PictureDecision::Skip(reason) => reason, + PictureDecision::Decode(state) => panic!("unexpected decode of POC {}", state.poc), + } +} + +fn pocs(dpb: &DecodeDpb) -> Vec<(i32, bool)> { + dpb.references() + .iter() + .map(|r| (r.poc, r.long_term)) + .collect() +} + +fn idr() -> SliceHeader { + SliceHeader { + first_slice_segment_in_pic_flag: true, + pic_output_flag: true, + ..Default::default() + } +} + +/// The POC MSB is predicted from `prevTid0Pic` only: a picture of a higher +/// sub-layer, or a sub-layer non-reference one, sitting between two TemporalId +/// 0 pictures must not move the prediction, or the LSB wrap is misread. +#[test] +fn poc_wraps_against_prev_tid0_pic() { + for (between, tid) in [ + (NalType::TsaR, 1), + (NalType::TrailN, 0), + (NalType::RadlR, 0), + ] { + let sps = sps(2); + let mut dpb = DecodeDpb::new(6, Arc::default()); + let keep_all = |lsb, deltas: &[i32]| { + let s0: Vec<_> = deltas.iter().map(|d| (*d, true)).collect(); + slice(lsb, &s0, &[]) + }; + let mut poc = |nal_type, tid, slice: &SliceHeader| { + decode(&mut dpb, nal(nal_type, tid), slice, &sps).0.poc + }; + assert_eq!(poc(NalType::IdrNLp, 0, &idr()), 0); + assert_eq!(poc(NalType::TrailR, 0, &keep_all(8, &[-8])), 8); + // LSB 0 after 8 is a wrap: 16. + assert_eq!(poc(NalType::TrailR, 0, &keep_all(0, &[-8])), 16); + // LSB 12 is just before 16, not after it. + assert_eq!(poc(between, tid, &keep_all(12, &[-4])), 12, "{between:?}"); + // Still predicted from 16 (LSB 0): LSB 8 is 24. Predicting from 12 + // instead would give 8. + assert_eq!( + poc(NalType::TrailR, 0, &keep_all(8, &[-8])), + 24, + "{between:?}" + ); + } +} + +/// A picture with POC below the last TemporalId 0 picture's, across the +/// wrap, gets a negative MSB step. +#[test] +fn poc_steps_back_across_the_wrap() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + decode(&mut dpb, nal(NalType::IdrWRadl, 0), &idr(), &sps); + // A RADL picture with LSB 15 right after an IDR is POC -1. + let (state, _) = decode( + &mut dpb, + nal(NalType::RadlN, 0), + &slice(15, &[], &[(1, true)]), + &sps, + ); + assert_eq!(state.poc, -1); + assert!(!state.missing_reference); +} + +/// The set names what stays: pictures left out are dropped before the current +/// picture is decoded, and their slots come free for it. +#[test] +fn rps_keeps_what_it_names_and_maps_slots() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(3, Arc::default()); + let (_, slot0) = decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + let (s1, slot1) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(1, &[(-1, true)], &[]), + &sps, + ); + assert_eq!( + s1.rps.st_curr_before, + vec![RpsEntry { + poc: 0, + slot: Some(slot0) + }] + ); + + // POC 2 uses 1 and keeps 0 for later. + let (s2, slot2) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(2, &[(-1, true), (-2, false)], &[]), + &sps, + ); + assert_eq!( + s2.rps.st_curr_before, + vec![RpsEntry { + poc: 1, + slot: Some(slot1) + }] + ); + assert_eq!( + s2.rps.st_foll, + vec![RpsEntry { + poc: 0, + slot: Some(slot0) + }] + ); + assert_eq!(pocs(&dpb), vec![(0, false), (1, false), (2, false)]); + + // All three slots are taken. POC 3 keeps only 2, so 0 and 1 go before it + // needs a slot, and it gets one of theirs. + let (s3, slot3) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(3, &[(-1, true)], &[]), + &sps, + ); + assert_eq!( + s3.rps.st_curr_before, + vec![RpsEntry { + poc: 2, + slot: Some(slot2) + }] + ); + assert!(slot3 == slot0 || slot3 == slot1); + assert_eq!(pocs(&dpb), vec![(2, false), (3, false)]); + + // A B picture between them: StCurrAfter. + let (b, _) = decode( + &mut dpb, + nal(NalType::TrailN, 0), + &slice(5, &[(-2, true)], &[(1, false)]), + &sps, + ); + assert_eq!(b.rps.st_curr_before[0].poc, 3); + assert_eq!(b.rps.st_foll[0].poc, 6); + assert!(b.rps.st_foll[0].slot.is_none(), "POC 6 is not decoded yet"); + assert!(!b.missing_reference, "a missing Foll entry is harmless"); +} + +/// A picture predicting from something the DPB does not hold says so. +#[test] +fn missing_reference_is_reported() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(4, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + let (state, _) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(4, &[(-2, true), (-4, true)], &[]), + &sps, + ); + assert!(state.missing_reference); + assert_eq!(state.rps.st_curr_before[0], RpsEntry { poc: 2, slot: None }); + assert_eq!(state.rps.st_curr_before[1].poc, 0); + assert!(state.rps.st_curr_before[1].slot.is_some()); +} + +/// Long-term entries pick a picture by LSB (or by full POC with the MSB +/// present), mark it long-term, take it out of reach of the short-term lists, +/// and drop it once no set names it. +#[test] +fn long_term_pictures() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + let lt = |poc_lsb_lt, used, msb: Option| LongTermRef { + poc_lsb_lt, + used_by_curr_pic_lt: used, + delta_poc_msb_present_flag: msb.is_some(), + delta_poc_msb_cycle_lt: msb.unwrap_or(0), + delta_poc_msb_cycle_lt_accumulated: msb.unwrap_or(0), + lt_idx_sps: None, + }; + let with_lt = |lsb, s0: &[(i32, bool)], lts: Vec| SliceHeader { + long_term_refs: lts, + ..slice(lsb, s0, &[]) + }; + + let (_, slot0) = decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(1, &[(-1, true)], &[]), + &sps, + ); + let (s2, _) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &with_lt(2, &[(-1, true)], vec![lt(0, true, None)]), + &sps, + ); + assert_eq!( + s2.rps.lt_curr, + vec![RpsEntry { + poc: 0, + slot: Some(slot0) + }] + ); + assert_eq!(pocs(&dpb), vec![(0, true), (1, false), (2, false)]); + + // Kept for later only; and by full POC with the MSB (cycle 0 is this + // picture's MSB, so POC 0 itself). + let (s3, _) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &with_lt(3, &[(-1, true)], vec![lt(0, false, Some(0))]), + &sps, + ); + assert_eq!( + s3.rps.lt_foll, + vec![RpsEntry { + poc: 0, + slot: Some(slot0) + }] + ); + assert!(s3.rps.lt_curr.is_empty()); + + // A long-term picture is not a short-term candidate any more. + let (s4, _) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &with_lt(4, &[(-1, true), (-4, true)], vec![lt(0, false, None)]), + &sps, + ); + assert_eq!(s4.rps.st_curr_before[1], RpsEntry { poc: 0, slot: None }); + assert!(s4.missing_reference); + + // Named by nothing: gone, slot free. + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(5, &[(-1, true)], &[]), + &sps, + ); + assert!(dpb.references().iter().all(|r| r.poc != 0 && !r.long_term)); +} + +/// The MSB cycle reaches a long-term picture a whole LSB period back. +#[test] +fn long_term_msb_cycle() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + let (_, slot0) = decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + let keep0 = |lsb| SliceHeader { + long_term_refs: vec![LongTermRef { + poc_lsb_lt: 0, + used_by_curr_pic_lt: true, + delta_poc_msb_present_flag: true, + delta_poc_msb_cycle_lt: 1, + delta_poc_msb_cycle_lt_accumulated: 1, + lt_idx_sps: None, + }], + ..slice(lsb, &[], &[]) + }; + let lt_only = |lsb| SliceHeader { + long_term_refs: vec![LongTermRef { + poc_lsb_lt: 0, + used_by_curr_pic_lt: false, + delta_poc_msb_present_flag: false, + ..Default::default() + }], + ..slice(lsb, &[], &[]) + }; + // Walk up to POC 16 keeping POC 0 long-term by LSB. + for lsb in [4, 8, 12] { + decode(&mut dpb, nal(NalType::TrailR, 0), <_only(lsb), &sps); + } + // POC 16 has LSB 0, as POC 0 does; MSB cycle 1 makes it unambiguous. + let (state, _) = decode(&mut dpb, nal(NalType::TrailR, 0), &keep0(0), &sps); + assert_eq!(state.poc, 16); + assert_eq!( + state.rps.lt_curr, + vec![RpsEntry { + poc: 0, + slot: Some(slot0) + }] + ); +} + +/// A stream starting at a CRA: RASL pictures are skipped, RADL ones decode. +#[test] +fn cra_first_skips_rasl() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + assert_eq!( + skip_reason(&mut dpb, nal(NalType::TrailR, 0), &slice(3, &[], &[]), &sps), + SkipReason::AwaitingIrap + ); + // The CRA's own set keeps an earlier picture for its RASL pictures. + let (cra, cra_slot) = decode( + &mut dpb, + nal(NalType::Cra, 0), + &slice(8, &[(-2, false)], &[]), + &sps, + ); + assert_eq!(cra.poc, 8, "the MSB starts at 0"); + assert!(cra.is_keyframe && cra.no_rasl_output_flag); + assert!(cra.no_output_of_prior_pics, "inferred for a CRA"); + assert!(!cra.missing_reference); + assert_eq!( + skip_reason( + &mut dpb, + nal(NalType::RaslN, 0), + &slice(6, &[(-2, true)], &[(2, true)]), + &sps + ), + SkipReason::RaslWithoutReferences + ); + let (radl, _) = decode( + &mut dpb, + nal(NalType::RadlN, 0), + &slice(7, &[], &[(1, true)]), + &sps, + ); + assert_eq!(radl.poc, 7); + assert_eq!( + radl.rps.st_curr_after, + vec![RpsEntry { + poc: 8, + slot: Some(cra_slot) + }] + ); + let (trail, _) = decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(9, &[(-1, true)], &[]), + &sps, + ); + assert!(!trail.is_keyframe && !trail.missing_reference); +} + +/// A CRA mid-stream is an ordinary picture, and its RASL pictures decode. +#[test] +fn cra_mid_stream_keeps_rasl() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(2, &[(-2, true)], &[]), + &sps, + ); + let (cra, _) = decode( + &mut dpb, + nal(NalType::Cra, 0), + &slice(4, &[(-2, false)], &[]), + &sps, + ); + assert!(!cra.is_keyframe && !cra.no_rasl_output_flag); + let (rasl, _) = decode( + &mut dpb, + nal(NalType::RaslN, 0), + &slice(3, &[(-1, true)], &[(1, true)]), + &sps, + ); + assert_eq!(rasl.poc, 3); + assert!(!rasl.missing_reference); +} + +/// After an end of sequence the next picture must be an IRAP, which starts +/// over: MSB 0, nothing kept, RASL pictures skipped. +#[test] +fn end_of_sequence_restarts() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + for lsb in [6u32, 12, 2] { + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(lsb, &[(-6, true)], &[]), + &sps, + ); + } + assert_eq!(pocs(&dpb).last(), Some(&(18, false))); + + dpb.end_of_sequence(); + assert_eq!( + skip_reason( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(4, &[(-1, true)], &[]), + &sps + ), + SkipReason::AwaitingIrap + ); + let (cra, _) = decode( + &mut dpb, + nal(NalType::Cra, 0), + &slice(4, &[(-2, false)], &[]), + &sps, + ); + assert_eq!(cra.poc, 4, "not 20: the MSB restarts"); + assert!(cra.is_keyframe); + assert_eq!(pocs(&dpb), vec![(4, false)]); + assert_eq!( + skip_reason( + &mut dpb, + nal(NalType::RaslR, 0), + &slice(3, &[(-1, true)], &[(1, true)]), + &sps + ), + SkipReason::RaslWithoutReferences + ); +} + +/// A BLA picture starts a new sequence wherever it is. +#[test] +fn bla_starts_a_new_sequence() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(6, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(5, &[(-5, true)], &[]), + &sps, + ); + let mut header = slice(9, &[(-4, false)], &[]); + header.no_output_of_prior_pics_flag = false; + let (bla, _) = decode(&mut dpb, nal(NalType::BlaWLp, 0), &header, &sps); + assert_eq!(bla.poc, 9); + assert!(bla.is_keyframe); + assert!( + !bla.no_output_of_prior_pics, + "taken from the flag for a BLA" + ); + assert_eq!(bla.rps.st_foll, vec![RpsEntry { poc: 5, slot: None }]); + assert_eq!(pocs(&dpb), vec![(9, false)]); + assert_eq!( + skip_reason( + &mut dpb, + nal(NalType::RaslN, 0), + &slice(8, &[], &[(1, true)]), + &sps + ), + SkipReason::RaslWithoutReferences + ); +} + +/// An IDR clears the DPB; its own reference picture set is empty. +#[test] +fn idr_clears_everything() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(3, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + decode( + &mut dpb, + nal(NalType::TrailR, 0), + &slice(1, &[(-1, true)], &[]), + &sps, + ); + let mut header = idr(); + header.no_output_of_prior_pics_flag = true; + let (state, slot) = decode(&mut dpb, nal(NalType::IdrWRadl, 0), &header, &sps); + assert_eq!(state.poc, 0); + assert_eq!(state.rps, RefPicSet::default()); + assert!(state.no_output_of_prior_pics); + assert_eq!(slot, 0, "every slot was freed"); + assert_eq!(pocs(&dpb), vec![(0, false)]); +} + +/// A sub-layer non-reference picture of the highest sub-layer is never +/// referenced, so its slot is released at once; in a lower sub-layer it is +/// kept, since higher sub-layers may predict from it. +#[test] +fn sub_layer_non_reference_pictures() { + let one_layer = sps(1); + let mut dpb = DecodeDpb::new(4, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &one_layer); + let (state, slot) = decode( + &mut dpb, + nal(NalType::TrailN, 0), + &slice(1, &[(-1, true)], &[]), + &one_layer, + ); + assert!(!state.is_reference); + assert_eq!(dpb.try_allocate_slot(), Some(slot), "released"); + + let two_layers = sps(2); + let mut dpb = DecodeDpb::new(4, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &two_layers); + let (state, _) = decode( + &mut dpb, + nal(NalType::TrailN, 0), + &slice(2, &[(-2, true)], &[]), + &two_layers, + ); + assert!(state.is_reference); + let (state, _) = decode( + &mut dpb, + nal(NalType::TsaN, 1), + &slice(1, &[(-1, true)], &[(1, true)]), + &two_layers, + ); + assert!(!state.is_reference); + assert!(!state.missing_reference); +} + +/// pic_output_flag passes through. +#[test] +fn pic_output_flag_passes_through() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(4, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + let mut header = slice(1, &[(-1, true)], &[]); + header.pic_output_flag = false; + let (state, _) = decode(&mut dpb, nal(NalType::TrailR, 0), &header, &sps); + assert!(!state.pic_output_flag); +} + +#[test] +fn vulkan_lists_are_padded() { + let list = [ + RpsEntry { + poc: 1, + slot: Some(3), + }, + RpsEntry { poc: 2, slot: None }, + ]; + let out = RefPicSet::vulkan_list(&list).unwrap(); + assert_eq!( + out, + [3, NO_REFERENCE_PICTURE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] + ); + let too_long = [RpsEntry { + poc: 0, + slot: Some(0), + }; 9]; + assert!(RefPicSet::vulkan_list(&too_long).is_err()); +} + +/// A slot a handed-out frame is reading must not be decoded over, even once +/// the reference picture set has dropped it. +#[test] +fn pinned_slots_are_not_reallocated() { + let sps = sps(1); + let pins = Arc::new(SlotPins::default()); + let mut dpb = DecodeDpb::new(2, pins.clone()); + let (_, idr_slot) = decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + pins.pin(idr_slot); + assert!(dpb.has_pinned_slots()); + + // POC 1 drops the IDR, but the caller still holds it. + let (_, slot1) = decode(&mut dpb, nal(NalType::TrailR, 0), &slice(1, &[], &[]), &sps); + assert_ne!(slot1, idr_slot); + let state = match dpb + .begin_picture( + &nal(NalType::TrailR, 0), + &slice(2, &[(-1, true)], &[]), + &sps, + ) + .unwrap() + { + PictureDecision::Decode(state) => state, + PictureDecision::Skip(_) => unreachable!(), + }; + assert_eq!(dpb.try_allocate_slot(), None, "one reference, one pinned"); + + pins.release(idr_slot); + assert!(!dpb.has_pinned_slots()); + let slot = dpb.try_allocate_slot().unwrap(); + assert_eq!(slot, idr_slot); + dpb.end_picture(slot, &state); +} + +/// Malformed input is an error, not a panic. +#[test] +fn rejects_bad_input() { + let sps = sps(1); + let mut dpb = DecodeDpb::new(4, Arc::default()); + decode(&mut dpb, nal(NalType::IdrNLp, 0), &idr(), &sps); + // A non-IDR picture without any short-term set. + let header = SliceHeader { + slice_pic_order_cnt_lsb: 1, + first_slice_segment_in_pic_flag: true, + ..Default::default() + }; + assert!( + dpb.begin_picture(&nal(NalType::TrailR, 0), &header, &sps) + .is_err() + ); + // Not a slice. + assert!( + dpb.begin_picture(&nal(NalType::Sps, 0), &idr(), &sps) + .is_err() + ); +} diff --git a/src/decoder/h265/mod.rs b/src/decoder/h265/mod.rs new file mode 100644 index 00000000..09daf8d8 --- /dev/null +++ b/src/decoder/h265/mod.rs @@ -0,0 +1,62 @@ +//! H.265 decoding: the host-side syntax parsing and reference bookkeeping. +//! +//! - [`parser`]: NAL units, the shared bit reader, `profile_tier_level()`, +//! HRD skipping and the VPS. +//! - [`sps`], [`pps`]: the parameter sets, with the scaling lists ([`scaling`]) +//! and short-term reference picture sets ([`rps`]) they contain. +//! - [`slice`]: the slice segment header, up to the reference syntax. +//! - [`dpb`]: picture order count, the reference picture set, random access +//! and DPB slots. +//! +//! None of this touches Vulkan; the session code fills the `StdVideoH265*` +//! structures from these types, whose field names follow the standard. + +pub(crate) mod dpb; +pub(crate) mod parser; +pub(crate) mod pps; +pub(crate) mod rps; +pub(crate) mod scaling; +pub(crate) mod slice; +pub(crate) mod sps; + +mod record; +mod session; +mod std_params; + +pub(crate) use session::H265Decoder; + +/// Parser and DPB tests against real x265 and NVENC streams (host-side, no +/// GPU), compiled only under `cargo test`. +#[cfg(test)] +mod parser_tests; + +/// Hand-written slice header syntax the fixtures do not cover. +#[cfg(test)] +mod syntax_tests; + +/// Byte offsets at which each coded picture begins in an Annex B stream. +/// +/// A new picture begins at each VCL NAL unit with +/// `first_slice_segment_in_pic_flag` set. Parameter sets, SEI, access unit +/// delimiters and end of sequence NAL units are attached to the picture that +/// follows them, so the offsets carve the stream up without loss. +pub(crate) fn picture_starts(stream: &[u8]) -> Vec { + let mut starts = Vec::new(); + let mut pending_start: Option = None; + for (offset, nal) in parser::iter_nal_units_with_offsets(stream) { + if nal.nal_type().is_vcl() { + if slice::starts_picture(&nal) { + starts.push(pending_start.take().unwrap_or(offset)); + } + pending_start = None; + } else if pending_start.is_none() { + pending_start = Some(offset); + } + } + starts +} + +/// How many leading bytes of `buffer` hold pictures known to be complete. +pub(crate) fn complete_prefix(buffer: &[u8]) -> usize { + picture_starts(buffer).last().copied().unwrap_or(0) +} diff --git a/src/decoder/h265/parser.rs b/src/decoder/h265/parser.rs new file mode 100644 index 00000000..7263f252 --- /dev/null +++ b/src/decoder/h265/parser.rs @@ -0,0 +1,699 @@ +//! H.265 Annex B parsing: NAL unit splitting and headers, the bit reader the +//! other syntax parsers share, `profile_tier_level()`, `hrd_parameters()` and +//! the video parameter set. +//! +//! The SPS, PPS and slice segment header live in their own modules +//! ([`super::sps`], [`super::pps`], [`super::slice`]); this one holds what they +//! have in common. +//! +//! As with H.264, this is not a full parser. Vulkan Video consumes the raw +//! slice segment NAL units and parses the slice data in the driver; the host +//! needs only enough syntax to fill the `StdVideoH265*` parameter sets, derive +//! the picture order count and drive the reference picture set. + +use crate::decoder::annexb; +use crate::decoder::bitreader::BitReader; +use crate::error::{PixelForgeError, Result}; + +/// Highest number of temporal sub-layers (`sps_max_sub_layers_minus1` is at +/// most 6). +pub(crate) const MAX_SUB_LAYERS: usize = 7; + +/// Build the error every parser in this module returns for bad syntax. +pub(crate) fn invalid(message: impl std::fmt::Display) -> PixelForgeError { + PixelForgeError::InvalidInput(format!("H.265: {message}")) +} + +/// `Ceil(Log2(x))`, the width of the fixed-length index fields. +pub(crate) fn ceil_log2(x: u32) -> u32 { + if x <= 1 { + 0 + } else { + 32 - (x - 1).leading_zeros() + } +} + +/// H.265 NAL unit types (Table 7-1). +/// +/// The reserved and unspecified values are kept as [`NalType::Other`] with +/// their raw value, so the range helpers still classify them correctly. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum NalType { + /// 0: trailing picture, sub-layer non-reference. + TrailN, + /// 1: trailing picture, sub-layer reference. + TrailR, + /// 2: temporal sub-layer access, sub-layer non-reference. + TsaN, + /// 3: temporal sub-layer access, sub-layer reference. + TsaR, + /// 4: step-wise temporal sub-layer access, sub-layer non-reference. + StsaN, + /// 5: step-wise temporal sub-layer access, sub-layer reference. + StsaR, + /// 6: random access decodable leading picture, sub-layer non-reference. + RadlN, + /// 7: random access decodable leading picture, sub-layer reference. + RadlR, + /// 8: random access skipped leading picture, sub-layer non-reference. + RaslN, + /// 9: random access skipped leading picture, sub-layer reference. + RaslR, + /// 16: broken link access, may have RASL and RADL leading pictures. + BlaWLp, + /// 17: broken link access, may have RADL leading pictures only. + BlaWRadl, + /// 18: broken link access, no leading pictures. + BlaNLp, + /// 19: instantaneous decoding refresh, may have RADL leading pictures. + IdrWRadl, + /// 20: instantaneous decoding refresh, no leading pictures. + IdrNLp, + /// 21: clean random access. + Cra, + /// 32: video parameter set. + Vps, + /// 33: sequence parameter set. + Sps, + /// 34: picture parameter set. + Pps, + /// 35: access unit delimiter. + Aud, + /// 36: end of sequence. + Eos, + /// 37: end of bitstream. + Eob, + /// 38: filler data. + Fd, + /// 39: prefix supplemental enhancement information. + SeiPrefix, + /// 40: suffix supplemental enhancement information. + SeiSuffix, + /// Any reserved or unspecified type, by raw value. + Other(u8), +} + +impl NalType { + pub fn from_raw(raw: u8) -> Self { + match raw { + 0 => NalType::TrailN, + 1 => NalType::TrailR, + 2 => NalType::TsaN, + 3 => NalType::TsaR, + 4 => NalType::StsaN, + 5 => NalType::StsaR, + 6 => NalType::RadlN, + 7 => NalType::RadlR, + 8 => NalType::RaslN, + 9 => NalType::RaslR, + 16 => NalType::BlaWLp, + 17 => NalType::BlaWRadl, + 18 => NalType::BlaNLp, + 19 => NalType::IdrWRadl, + 20 => NalType::IdrNLp, + 21 => NalType::Cra, + 32 => NalType::Vps, + 33 => NalType::Sps, + 34 => NalType::Pps, + 35 => NalType::Aud, + 36 => NalType::Eos, + 37 => NalType::Eob, + 38 => NalType::Fd, + 39 => NalType::SeiPrefix, + 40 => NalType::SeiSuffix, + other => NalType::Other(other), + } + } + + /// The `nal_unit_type` value. + pub fn raw(self) -> u8 { + match self { + NalType::TrailN => 0, + NalType::TrailR => 1, + NalType::TsaN => 2, + NalType::TsaR => 3, + NalType::StsaN => 4, + NalType::StsaR => 5, + NalType::RadlN => 6, + NalType::RadlR => 7, + NalType::RaslN => 8, + NalType::RaslR => 9, + NalType::BlaWLp => 16, + NalType::BlaWRadl => 17, + NalType::BlaNLp => 18, + NalType::IdrWRadl => 19, + NalType::IdrNLp => 20, + NalType::Cra => 21, + NalType::Vps => 32, + NalType::Sps => 33, + NalType::Pps => 34, + NalType::Aud => 35, + NalType::Eos => 36, + NalType::Eob => 37, + NalType::Fd => 38, + NalType::SeiPrefix => 39, + NalType::SeiSuffix => 40, + NalType::Other(raw) => raw, + } + } + + /// A coded slice segment (types 0..=31), including the reserved ones. + pub fn is_vcl(self) -> bool { + self.raw() < 32 + } + + /// An intra random access point picture: BLA, IDR, CRA or a reserved IRAP + /// type (16..=23). + pub fn is_irap(self) -> bool { + (16..=23).contains(&self.raw()) + } + + /// An IDR picture, with or without RADL leading pictures. + pub fn is_idr(self) -> bool { + matches!(self, NalType::IdrWRadl | NalType::IdrNLp) + } + + /// A broken link access picture. + pub fn is_bla(self) -> bool { + matches!(self, NalType::BlaWLp | NalType::BlaWRadl | NalType::BlaNLp) + } + + /// A random access skipped leading picture. + pub fn is_rasl(self) -> bool { + matches!(self, NalType::RaslN | NalType::RaslR) + } + + /// A random access decodable leading picture. + pub fn is_radl(self) -> bool { + matches!(self, NalType::RadlN | NalType::RadlR) + } + + /// A sub-layer non-reference picture: the even types up to 14 + /// (`TRAIL_N`, `TSA_N`, `STSA_N`, `RADL_N`, `RASL_N` and the reserved + /// `RSV_VCL_N10/12/14`). + /// + /// Such a picture is never referenced by a picture of the same temporal + /// sub-layer, but a higher sub-layer may still reference it. + pub fn is_sub_layer_non_reference(self) -> bool { + let raw = self.raw(); + raw <= 14 && raw.is_multiple_of(2) + } +} + +/// The two-byte NAL unit header (clause 7.3.1.2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct NalHeader { + pub nal_type: NalType, + /// `nuh_layer_id`. Anything above 0 belongs to a layer this decoder does + /// not decode (scalable or multiview extensions) and should be dropped. + pub layer_id: u8, + /// `TemporalId`, which is `nuh_temporal_id_plus1 - 1`. + pub temporal_id: u8, +} + +/// Parse the NAL unit header from the bytes after the start code. +pub(crate) fn parse_nal_header(nal: &[u8]) -> Result { + if nal.len() < 2 { + return Err(invalid("NAL unit shorter than its header")); + } + if nal[0] & 0x80 != 0 { + return Err(invalid("forbidden_zero_bit is set")); + } + let temporal_id_plus1 = nal[1] & 0x7; + if temporal_id_plus1 == 0 { + return Err(invalid("nuh_temporal_id_plus1 is 0")); + } + Ok(NalHeader { + nal_type: NalType::from_raw((nal[0] >> 1) & 0x3F), + layer_id: ((nal[0] & 1) << 5) | (nal[1] >> 3), + temporal_id: temporal_id_plus1 - 1, + }) +} + +/// A single NAL unit within an Annex B stream. +#[derive(Debug, Clone, Copy)] +pub(crate) struct NalUnit<'a> { + pub header: NalHeader, + /// The complete NAL unit (2-byte header + EBSP payload), without start code. + pub data: &'a [u8], +} + +impl<'a> NalUnit<'a> { + pub fn nal_type(&self) -> NalType { + self.header.nal_type + } + + /// EBSP payload after the 2-byte NAL header. + pub fn payload(&self) -> &'a [u8] { + &self.data[2..] + } +} + +/// Iterate over NAL units in an Annex B byte stream (3- or 4-byte start codes). +/// +/// NAL units whose header is malformed (too short, forbidden bit set) are +/// skipped rather than ending the iteration. +pub(crate) fn iter_nal_units(data: &[u8]) -> impl Iterator> { + iter_nal_units_with_offsets(data).map(|(_, nal)| nal) +} + +/// Like [`iter_nal_units`], but also yields each NAL's start-code offset within +/// `data`. +pub(crate) fn iter_nal_units_with_offsets( + data: &[u8], +) -> impl Iterator)> { + annexb::iter_nal_units(data).filter_map(|(offset, nal)| { + let header = parse_nal_header(nal).ok()?; + Some((offset, NalUnit { header, data: nal })) + }) +} + +/// [`BitReader`] plus a count of the RBSP bits consumed. +/// +/// The count excludes emulation prevention bytes, which is what Vulkan's +/// `NumBitsForSTRefPicSetInSlice` is measured in. `ue(v)`/`se(v)` are +/// reimplemented here on top of [`BitReader::bit`] so every bit is counted. +pub(crate) struct Reader<'a> { + inner: BitReader<'a>, + position: usize, +} + +impl<'a> Reader<'a> { + pub fn new(payload: &'a [u8]) -> Self { + Self { + inner: BitReader::new(payload), + position: 0, + } + } + + /// RBSP bits consumed so far. + pub fn position(&self) -> usize { + self.position + } + + pub fn bit(&mut self) -> Result { + let bit = self.inner.bit()?; + self.position += 1; + Ok(bit) + } + + /// `u(n)`, n <= 32. + pub fn bits(&mut self, n: u32) -> Result { + debug_assert!(n <= 32); + let mut v = 0u64; + for _ in 0..n { + v = (v << 1) | self.bit()? as u64; + } + Ok(v as u32) + } + + /// `u(1)` as a boolean. + pub fn flag(&mut self) -> Result { + Ok(self.bit()? != 0) + } + + /// Skip `n` bits. + pub fn skip(&mut self, n: u32) -> Result<()> { + for _ in 0..n { + self.bit()?; + } + Ok(()) + } + + /// `ue(v)`. + pub fn ue(&mut self) -> Result { + let mut leading_zeros = 0u32; + while self.bit()? == 0 { + leading_zeros += 1; + if leading_zeros > 31 { + return Err(invalid("invalid Exp-Golomb code")); + } + } + if leading_zeros == 0 { + return Ok(0); + } + let suffix = self.bits(leading_zeros)? as u64; + Ok(((1u64 << leading_zeros) - 1 + suffix) as u32) + } + + /// `ue(v)` that must not exceed `max`; `what` names the syntax element. + pub fn ue_max(&mut self, max: u32, what: &str) -> Result { + let v = self.ue()?; + if v > max { + return Err(invalid(format_args!("{what} {v} exceeds {max}"))); + } + Ok(v) + } + + /// `se(v)`. + pub fn se(&mut self) -> Result { + let code = self.ue()? as i64; + // Mapping: 0 -> 0, 1 -> 1, 2 -> -1, 3 -> 2, 4 -> -2, ... + let value = (code + 1) / 2; + Ok(if code % 2 == 0 { -value } else { value } as i32) + } + + /// `se(v)` that must lie within `min..=max`. + pub fn se_range(&mut self, min: i32, max: i32, what: &str) -> Result { + let v = self.se()?; + if v < min || v > max { + return Err(invalid(format_args!("{what} {v} outside {min}..={max}"))); + } + Ok(v) + } +} + +/// The general part of `profile_tier_level()` (clause 7.3.3), which is what +/// `StdVideoH265ProfileTierLevel` carries. Sub-layer profiles and levels are +/// parsed past but not kept. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct ProfileTierLevel { + pub general_profile_space: u8, + pub general_tier_flag: bool, + /// 1 = Main, 2 = Main 10, 3 = Main Still Picture, 4 = format range + /// extensions, and so on. + pub general_profile_idc: u8, + /// `general_profile_compatibility_flag[j]`, bit 31 - j. + pub general_profile_compatibility_flags: u32, + pub general_progressive_source_flag: bool, + pub general_interlaced_source_flag: bool, + pub general_non_packed_constraint_flag: bool, + pub general_frame_only_constraint_flag: bool, + /// Raw `general_level_idc`: 30 times the level number (level 4.1 is 123). + /// `StdVideoH265LevelIdc` is an enumeration instead, so it needs mapping. + pub general_level_idc: u8, +} + +/// Parse `profile_tier_level(1, max_sub_layers_minus1)`. +pub(crate) fn parse_profile_tier_level( + r: &mut Reader, + max_sub_layers_minus1: u8, +) -> Result { + let mut ptl = ProfileTierLevel { + general_profile_space: r.bits(2)? as u8, + general_tier_flag: r.flag()?, + general_profile_idc: r.bits(5)? as u8, + general_profile_compatibility_flags: r.bits(32)?, + general_progressive_source_flag: r.flag()?, + general_interlaced_source_flag: r.flag()?, + general_non_packed_constraint_flag: r.flag()?, + general_frame_only_constraint_flag: r.flag()?, + ..Default::default() + }; + // 43 bits of profile-specific constraint flags, then general_inbld_flag + // (or a reserved bit). None of them matter to the decoder. + r.skip(44)?; + ptl.general_level_idc = r.bits(8)? as u8; + + let sub_layers = max_sub_layers_minus1 as usize; + let mut profile_present = [false; MAX_SUB_LAYERS]; + let mut level_present = [false; MAX_SUB_LAYERS]; + for i in 0..sub_layers { + profile_present[i] = r.flag()?; + level_present[i] = r.flag()?; + } + if sub_layers > 0 { + // reserved_zero_2bits for each unused sub-layer slot up to 8. + r.skip(2 * (8 - sub_layers as u32))?; + } + for i in 0..sub_layers { + if profile_present[i] { + // Profile space, tier, profile idc, 32 compatibility flags, the + // four source flags and 44 constraint/reserved bits. + r.skip(2 + 1 + 5 + 32 + 4 + 44)?; + } + if level_present[i] { + r.skip(8)?; + } + } + Ok(ptl) +} + +/// Per-sub-layer DPB sizing, as `StdVideoH265DecPicBufMgr` carries it. +/// +/// Every entry is filled: when the bitstream only codes the highest +/// sub-layer (`*_sub_layer_ordering_info_present_flag` = 0), the lower ones +/// are inferred equal to it (clause 7.4.3.2.1). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct DecPicBufMgr { + pub max_dec_pic_buffering_minus1: [u8; MAX_SUB_LAYERS], + pub max_num_reorder_pics: [u8; MAX_SUB_LAYERS], + pub max_latency_increase_plus1: [u32; MAX_SUB_LAYERS], +} + +/// Parse the sub-layer ordering loop shared by the VPS and SPS. +pub(crate) fn parse_dec_pic_buf_mgr( + r: &mut Reader, + max_sub_layers_minus1: u8, + info_present: bool, +) -> Result { + let mut mgr = DecPicBufMgr::default(); + let highest = max_sub_layers_minus1 as usize; + let first = if info_present { 0 } else { highest }; + for i in first..=highest { + // MaxDpbSize is at most 16, so max_dec_pic_buffering_minus1 <= 15. + mgr.max_dec_pic_buffering_minus1[i] = r.ue_max(15, "max_dec_pic_buffering_minus1")? as u8; + mgr.max_num_reorder_pics[i] = r.ue_max( + mgr.max_dec_pic_buffering_minus1[i] as u32, + "max_num_reorder_pics", + )? as u8; + mgr.max_latency_increase_plus1[i] = r.ue()?; + } + for i in 0..first { + mgr.max_dec_pic_buffering_minus1[i] = mgr.max_dec_pic_buffering_minus1[highest]; + mgr.max_num_reorder_pics[i] = mgr.max_num_reorder_pics[highest]; + mgr.max_latency_increase_plus1[i] = mgr.max_latency_increase_plus1[highest]; + } + Ok(mgr) +} + +/// Parse `hrd_parameters()` (clause E.2.2) only to advance past it. +/// +/// Vulkan decode does not use HRD information, and it is large, so nothing is +/// kept; but it sits in the middle of the VPS and the VUI, so it has to be +/// parsed exactly. +pub(crate) fn skip_hrd_parameters( + r: &mut Reader, + common_inf_present: bool, + max_sub_layers_minus1: u8, +) -> Result<()> { + let mut nal_hrd = false; + let mut vcl_hrd = false; + let mut sub_pic_hrd_params_present = false; + if common_inf_present { + nal_hrd = r.flag()?; + vcl_hrd = r.flag()?; + if nal_hrd || vcl_hrd { + sub_pic_hrd_params_present = r.flag()?; + if sub_pic_hrd_params_present { + // tick_divisor_minus2, du_cpb_removal_delay_increment_length_minus1, + // sub_pic_cpb_params_in_pic_timing_sei_flag, + // dpb_output_delay_du_length_minus1. + r.skip(8 + 5 + 1 + 5)?; + } + // bit_rate_scale, cpb_size_scale. + r.skip(4 + 4)?; + if sub_pic_hrd_params_present { + // cpb_size_du_scale. + r.skip(4)?; + } + // initial_cpb_removal_delay_length_minus1, + // au_cpb_removal_delay_length_minus1, dpb_output_delay_length_minus1. + r.skip(5 + 5 + 5)?; + } + } + + for _ in 0..=max_sub_layers_minus1 { + let fixed_pic_rate_general = r.flag()?; + // Inferred to be 1 when fixed_pic_rate_general_flag is 1. + let fixed_pic_rate_within_cvs = fixed_pic_rate_general || r.flag()?; + let mut low_delay_hrd = false; + if fixed_pic_rate_within_cvs { + let _elemental_duration_in_tc_minus1 = r.ue()?; + } else { + low_delay_hrd = r.flag()?; + } + let mut cpb_cnt_minus1 = 0; + if !low_delay_hrd { + cpb_cnt_minus1 = r.ue_max(31, "cpb_cnt_minus1")?; + } + for present in [nal_hrd, vcl_hrd] { + if !present { + continue; + } + // sub_layer_hrd_parameters(). + for _ in 0..=cpb_cnt_minus1 { + let _bit_rate_value_minus1 = r.ue()?; + let _cpb_size_value_minus1 = r.ue()?; + if sub_pic_hrd_params_present { + let _cpb_size_du_value_minus1 = r.ue()?; + let _bit_rate_du_value_minus1 = r.ue()?; + } + let _cbr_flag = r.flag()?; + } + } + } + Ok(()) +} + +/// Timing information from a VPS (`vps_timing_info_present_flag`). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct VpsTiming { + pub num_units_in_tick: u32, + pub time_scale: u32, + pub poc_proportional_to_timing_flag: bool, + pub num_ticks_poc_diff_one_minus1: u32, +} + +/// Parsed video parameter set (clause 7.3.2.1): the fields +/// `StdVideoH265VideoParameterSet` needs. HRD parameters are parsed past and +/// dropped, and the VPS extension is not parsed. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct Vps { + pub vps_video_parameter_set_id: u8, + pub vps_base_layer_internal_flag: bool, + pub vps_base_layer_available_flag: bool, + pub vps_max_layers_minus1: u8, + pub vps_max_sub_layers_minus1: u8, + pub vps_temporal_id_nesting_flag: bool, + pub profile_tier_level: ProfileTierLevel, + pub vps_sub_layer_ordering_info_present_flag: bool, + pub dec_pic_buf_mgr: DecPicBufMgr, + pub vps_max_layer_id: u8, + pub vps_num_layer_sets_minus1: u32, + /// Present when `vps_timing_info_present_flag` is set. + pub timing: Option, + pub vps_num_hrd_parameters: u32, + pub vps_extension_flag: bool, +} + +/// Parse a VPS NAL payload (EBSP, after the 2-byte NAL header). +pub(crate) fn parse_vps(payload: &[u8]) -> Result { + let mut r = Reader::new(payload); + let mut vps = Vps { + vps_video_parameter_set_id: r.bits(4)? as u8, + vps_base_layer_internal_flag: r.flag()?, + vps_base_layer_available_flag: r.flag()?, + vps_max_layers_minus1: r.bits(6)? as u8, + vps_max_sub_layers_minus1: r.bits(3)? as u8, + vps_temporal_id_nesting_flag: r.flag()?, + ..Default::default() + }; + if vps.vps_max_sub_layers_minus1 as usize >= MAX_SUB_LAYERS { + return Err(invalid("VPS: vps_max_sub_layers_minus1 is 7")); + } + let _vps_reserved_0xffff_16bits = r.bits(16)?; + vps.profile_tier_level = parse_profile_tier_level(&mut r, vps.vps_max_sub_layers_minus1)?; + vps.vps_sub_layer_ordering_info_present_flag = r.flag()?; + vps.dec_pic_buf_mgr = parse_dec_pic_buf_mgr( + &mut r, + vps.vps_max_sub_layers_minus1, + vps.vps_sub_layer_ordering_info_present_flag, + )?; + vps.vps_max_layer_id = r.bits(6)? as u8; + vps.vps_num_layer_sets_minus1 = r.ue_max(1023, "vps_num_layer_sets_minus1")?; + for _ in 1..=vps.vps_num_layer_sets_minus1 { + // layer_id_included_flag[i][j] for j in 0..=vps_max_layer_id. + r.skip(vps.vps_max_layer_id as u32 + 1)?; + } + if r.flag()? { + let mut timing = VpsTiming { + num_units_in_tick: r.bits(32)?, + time_scale: r.bits(32)?, + poc_proportional_to_timing_flag: r.flag()?, + num_ticks_poc_diff_one_minus1: 0, + }; + if timing.poc_proportional_to_timing_flag { + timing.num_ticks_poc_diff_one_minus1 = r.ue()?; + } + vps.timing = Some(timing); + vps.vps_num_hrd_parameters = + r.ue_max(vps.vps_num_layer_sets_minus1 + 1, "vps_num_hrd_parameters")?; + for i in 0..vps.vps_num_hrd_parameters { + let _hrd_layer_set_idx = r.ue()?; + // cprms_present_flag[0] is inferred to be 1. + let cprms_present = i == 0 || r.flag()?; + skip_hrd_parameters(&mut r, cprms_present, vps.vps_max_sub_layers_minus1)?; + } + } + vps.vps_extension_flag = r.flag()?; + Ok(vps) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn nal_iteration_and_header() { + let data = [ + 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0xAA, // VPS, layer 0, tid 0 + 0x00, 0x00, 0x01, 0x26, 0x01, 0xBB, // IDR_W_RADL + 0x00, 0x00, 0x01, 0x02, 0x03, 0xCC, // TRAIL_R, tid 2 + 0x00, 0x00, 0x01, 0x80, 0x01, // forbidden bit set: skipped + 0x00, 0x00, 0x01, 0x4F, 0xF9, 0xDD, // SEI prefix, nuh_layer_id 63 + ]; + let nals: Vec<_> = iter_nal_units(&data).collect(); + assert_eq!(nals.len(), 4); + assert_eq!(nals[0].nal_type(), NalType::Vps); + assert_eq!(nals[0].payload(), &[0xAA]); + assert_eq!(nals[1].nal_type(), NalType::IdrWRadl); + assert!(nals[1].nal_type().is_irap() && nals[1].nal_type().is_idr()); + assert_eq!(nals[2].nal_type(), NalType::TrailR); + assert_eq!(nals[2].header.temporal_id, 2); + // 0x4F 0xF9: type 39, layer id (1 << 5) | 31 = 63, tid 0. + assert_eq!(nals[3].nal_type(), NalType::SeiPrefix); + assert_eq!(nals[3].header.layer_id, 63); + } + + #[test] + fn nal_type_classes() { + for raw in 0..64u8 { + let t = NalType::from_raw(raw); + assert_eq!(t.raw(), raw, "round trip of {raw}"); + assert_eq!(t.is_vcl(), raw < 32); + assert_eq!(t.is_irap(), (16..=23).contains(&raw)); + assert_eq!(t.is_idr(), raw == 19 || raw == 20); + assert_eq!(t.is_rasl(), raw == 8 || raw == 9); + assert_eq!(t.is_radl(), raw == 6 || raw == 7); + } + assert!(NalType::TrailN.is_sub_layer_non_reference()); + assert!(NalType::RaslN.is_sub_layer_non_reference()); + assert!(!NalType::TrailR.is_sub_layer_non_reference()); + assert!(!NalType::Cra.is_sub_layer_non_reference()); + assert!(NalType::Other(14).is_sub_layer_non_reference()); + } + + #[test] + fn reader_counts_rbsp_bits_not_emulation_bytes() { + // 00 00 03 01: the 03 is not part of the RBSP. + let data = [0x00, 0x00, 0x03, 0x01]; + let mut r = Reader::new(&data); + assert_eq!(r.bits(24).unwrap(), 1); + assert_eq!(r.position(), 24); + } + + #[test] + fn reader_exp_golomb() { + // ue: 1 -> 0, 010 -> 1, 011 -> 2; se: 00100 -> code 3 -> +2, + // 00101 -> code 4 -> -2. + let data = [0b1010_0110, 0b0100_0010, 0b1000_0000]; + let mut r = Reader::new(&data); + assert_eq!(r.ue().unwrap(), 0); + assert_eq!(r.ue().unwrap(), 1); + assert_eq!(r.ue().unwrap(), 2); + assert_eq!(r.se().unwrap(), 2); + assert_eq!(r.se().unwrap(), -2); + assert_eq!(r.position(), 17); + } + + #[test] + fn ceil_log2_values() { + assert_eq!(ceil_log2(0), 0); + assert_eq!(ceil_log2(1), 0); + assert_eq!(ceil_log2(2), 1); + assert_eq!(ceil_log2(3), 2); + assert_eq!(ceil_log2(4), 2); + assert_eq!(ceil_log2(9), 4); + } +} diff --git a/src/decoder/h265/parser_tests.rs b/src/decoder/h265/parser_tests.rs new file mode 100644 index 00000000..709fe3cb --- /dev/null +++ b/src/decoder/h265/parser_tests.rs @@ -0,0 +1,701 @@ +//! Parser and DPB tests against real H.265 streams. +//! +//! The fixtures in `tests/data/*.265` are 176x144 (one is 172x140) testsrc2 +//! clips of 10 to 30 frames. All but one come from libx265 4.3 through +//! `ffmpeg -c:v libx265 -crf 45 -x265-params log-level=error:`: +//! +//! | fixture | x265 params | +//! |-----------------|-------------------------------------------------------------------| +//! | `opengop` | `open-gop=1:keyint=12:min-keyint=12:scenecut=0:bframes=3:b-pyramid=1` | +//! | `closedgop` | `open-gop=0:keyint=10:min-keyint=10:scenecut=0:bframes=3:radl=2:wpp=0` | +//! | `slices` | `slices=4:keyint=15:bframes=2` (3 slices of 3 CTB rows) | +//! | `main10` | `keyint=15:bframes=2`, `-pix_fmt yuv420p10le`, 172x140 | +//! | `temporal` | `temporal-layers=3:bframes=3:b-pyramid=1:keyint=20:scaling-list=default` | +//! | `scaling` | `scaling-list=:bframes=2` | +//! +//! `nvenc` is `hevc_nvenc -bf 3 -b_ref_mode middle -g 10 -rc constqp -qp 45`: +//! unlike x265 it codes a short-term set in the SPS and has VUI HRD +//! parameters. `cra_start` is `opengop` cut at its second CRA (keeping the +//! parameter sets), so it starts with a CRA whose RASL pictures reference +//! pictures that are not there. +//! +//! The expected values below were read from `ffmpeg -bsf:v trace_headers`, +//! which prints every syntax element with its bit position, and the frame +//! counts from `ffprobe -count_frames` (which skips those RASL pictures too). +//! A one-off comparison of every parsed VPS/SPS/PPS field and every slice +//! header field against the trace found no differences; the assertions here +//! keep the parts that matter. + +use std::collections::HashMap; +use std::sync::Arc; + +use super::dpb::{DecodeDpb, PictureDecision, RefPicSet, SkipReason}; +use super::parser::{NalType, NalUnit, Vps, iter_nal_units, parse_vps}; +use super::pps::{Pps, parse_pps}; +use super::slice::{SliceHeader, parse_slice_header, slice_pps_id, starts_picture}; +use super::sps::{Sps, parse_sps}; + +const OPENGOP: &[u8] = include_bytes!("../../../tests/data/opengop.265"); +const CLOSEDGOP: &[u8] = include_bytes!("../../../tests/data/closedgop.265"); +const SLICES: &[u8] = include_bytes!("../../../tests/data/slices.265"); +const MAIN10: &[u8] = include_bytes!("../../../tests/data/main10.265"); +const NVENC: &[u8] = include_bytes!("../../../tests/data/nvenc.265"); +const CRA_START: &[u8] = include_bytes!("../../../tests/data/cra_start.265"); +const TEMPORAL: &[u8] = include_bytes!("../../../tests/data/temporal.265"); +const SCALING: &[u8] = include_bytes!("../../../tests/data/scaling.265"); + +/// Every fixture, with the number of pictures it holds and the number ffmpeg +/// outputs. +const ALL: &[(&str, &[u8], usize, usize)] = &[ + ("opengop", OPENGOP, 30, 30), + ("closedgop", CLOSEDGOP, 30, 30), + ("slices", SLICES, 20, 20), + ("main10", MAIN10, 20, 20), + ("nvenc", NVENC, 20, 20), + ("cra_start", CRA_START, 8, 6), + ("temporal", TEMPORAL, 20, 20), + ("scaling", SCALING, 10, 10), +]; + +/// One slice segment with the parameter sets in effect for it. +struct Segment<'a> { + nal: NalUnit<'a>, + header: SliceHeader, + sps: Sps, +} + +/// The parameter sets of a stream and all of its slice segments, parsed. +struct Parsed<'a> { + vps: Vec, + sps: Vec, + pps: Vec, + segments: Vec>, +} + +fn parse_stream<'a>(name: &str, data: &'a [u8]) -> Parsed<'a> { + let mut parsed = Parsed { + vps: Vec::new(), + sps: Vec::new(), + pps: Vec::new(), + segments: Vec::new(), + }; + let mut sps_by_id: HashMap = HashMap::new(); + let mut pps_by_id: HashMap = HashMap::new(); + for nal in iter_nal_units(data) { + assert_eq!(nal.header.layer_id, 0, "{name}: base layer only"); + match nal.nal_type() { + NalType::Vps => parsed + .vps + .push(parse_vps(nal.payload()).unwrap_or_else(|e| panic!("{name}: VPS: {e}"))), + NalType::Sps => { + let sps = parse_sps(nal.payload()).unwrap_or_else(|e| panic!("{name}: SPS: {e}")); + sps_by_id.insert(sps.sps_seq_parameter_set_id, sps.clone()); + parsed.sps.push(sps); + } + NalType::Pps => { + let pps = parse_pps(nal.payload()).unwrap_or_else(|e| panic!("{name}: PPS: {e}")); + pps_by_id.insert(pps.pps_pic_parameter_set_id, pps.clone()); + parsed.pps.push(pps); + } + t if t.is_vcl() => { + let pps = pps_by_id[&slice_pps_id(&nal).unwrap()].clone(); + let sps = sps_by_id[&pps.pps_seq_parameter_set_id].clone(); + let header = parse_slice_header(&nal, &sps, &pps) + .unwrap_or_else(|e| panic!("{name}: slice header: {e}")); + assert_eq!( + header.first_slice_segment_in_pic_flag, + starts_picture(&nal), + "{name}: starts_picture agrees with the parsed header" + ); + parsed.segments.push(Segment { nal, header, sps }); + } + _ => {} + } + } + parsed +} + +impl Parsed<'_> { + /// The first segment of each picture. + fn pictures(&self) -> impl Iterator> { + self.segments + .iter() + .filter(|s| s.header.first_slice_segment_in_pic_flag) + } +} + +/// What the DPB made of one picture. +struct Outcome { + nal_type: NalType, + /// `None` when skipped. + poc: Option, + skip: Option, + rps: RefPicSet, + missing_reference: bool, + is_keyframe: bool, +} + +/// Run every picture of `parsed` through a [`DecodeDpb`] with exactly +/// `sps_max_dec_pic_buffering` slots, which a conforming stream never +/// overflows. +fn run_dpb(name: &str, parsed: &Parsed) -> Vec { + let first_sps = &parsed.sps[0]; + let mut dpb = DecodeDpb::new(first_sps.max_dec_pic_buffering(), Arc::default()); + let mut outcomes = Vec::new(); + for segment in parsed.pictures() { + let decision = dpb + .begin_picture(&segment.nal.header, &segment.header, &segment.sps) + .unwrap_or_else(|e| panic!("{name}: begin_picture: {e}")); + let state = match decision { + PictureDecision::Skip(reason) => { + outcomes.push(Outcome { + nal_type: segment.nal.nal_type(), + poc: None, + skip: Some(reason), + rps: RefPicSet::default(), + missing_reference: false, + is_keyframe: false, + }); + continue; + } + PictureDecision::Decode(state) => state, + }; + // Every slot the reference picture set names holds that picture, and + // the DPB holds nothing else. + let rps = &state.rps; + let named: Vec<_> = [ + &rps.st_curr_before, + &rps.st_curr_after, + &rps.st_foll, + &rps.lt_curr, + &rps.lt_foll, + ] + .into_iter() + .flatten() + .filter_map(|e| e.slot.map(|slot| (slot, e.poc))) + .collect(); + for &(slot, poc) in &named { + assert!( + dpb.references() + .iter() + .any(|r| r.slot == slot && r.poc == poc), + "{name}: slot {slot} holds POC {poc}" + ); + } + assert_eq!( + named.len(), + dpb.references().len(), + "{name}: DPB holds the RPS" + ); + let slot = dpb + .try_allocate_slot() + .unwrap_or_else(|| panic!("{name}: DPB overflow at POC {}", state.poc)); + dpb.end_picture(slot, &state); + outcomes.push(Outcome { + nal_type: state.nal_type, + poc: Some(state.poc), + skip: None, + rps: state.rps, + missing_reference: state.missing_reference, + is_keyframe: state.is_keyframe, + }); + } + outcomes +} + +/// Every NAL unit of every fixture parses, and the picture structure is what +/// the encoder was asked for. +#[test] +fn parses_every_nal_unit() { + for &(name, data, pictures, _) in ALL { + let parsed = parse_stream(name, data); + assert!(!parsed.vps.is_empty(), "{name}: has a VPS"); + assert!(!parsed.sps.is_empty(), "{name}: has an SPS"); + assert!(!parsed.pps.is_empty(), "{name}: has a PPS"); + assert_eq!(parsed.pictures().count(), pictures, "{name}: pictures"); + let first = parsed.pictures().next().unwrap(); + assert!(first.nal.nal_type().is_irap(), "{name}: opens with an IRAP"); + } + // Three slices per picture, at CTB rows 0, 1 and 2. + let parsed = parse_stream("slices", SLICES); + assert_eq!(parsed.segments.len(), 60); + let addresses: Vec = parsed.segments[..3] + .iter() + .map(|s| s.header.slice_segment_address) + .collect(); + assert_eq!(addresses, vec![0, 3, 6]); + // Every slice of a picture carries the same POC LSB and slice type. + for picture in parsed.segments.chunks(3) { + for s in &picture[1..] { + assert!(!s.header.first_slice_segment_in_pic_flag); + assert_eq!( + s.header.slice_pic_order_cnt_lsb, + picture[0].header.slice_pic_order_cnt_lsb + ); + assert_eq!(s.header.slice_type, picture[0].header.slice_type); + } + } +} + +/// SPS and VPS values against trace_headers. +#[test] +fn parses_sps_and_vps() { + for &(name, data, _, _) in ALL { + let parsed = parse_stream(name, data); + let sps = &parsed.sps[0]; + let vps = &parsed.vps[0]; + assert_eq!(sps.chroma_format_idc, 1, "{name}: 4:2:0"); + let display = if name == "main10" { + (172, 140) + } else { + (176, 144) + }; + assert_eq!(sps.display_dimensions(), display, "{name}: display size"); + assert_eq!( + sps.log2_max_pic_order_cnt_lsb_minus4, 4, + "{name}: 8-bit POC LSB" + ); + assert_eq!(sps.max_pic_order_cnt_lsb(), 256); + assert!(!sps.long_term_ref_pics_present_flag, "{name}: no long-term"); + assert_eq!( + vps.profile_tier_level, sps.profile_tier_level, + "{name}: VPS and SPS agree on the profile" + ); + assert_eq!(vps.vps_max_sub_layers_minus1, sps.sps_max_sub_layers_minus1); + assert_eq!( + vps.dec_pic_buf_mgr, sps.dec_pic_buf_mgr, + "{name}: DPB sizes" + ); + if name != "nvenc" { + // x265 always codes every short-term set in the slice header. + assert_eq!(sps.num_short_term_ref_pic_sets(), 0, "{name}"); + assert_eq!(sps.max_dec_pic_buffering(), 5, "{name}"); + assert_eq!(sps.reorder_depth(), 2, "{name}"); + assert_eq!(sps.profile_tier_level.general_level_idc, 60, "{name}"); + assert_eq!( + ( + sps.pic_width_in_luma_samples, + sps.pic_height_in_luma_samples + ), + (176, 144), + "{name}: coded size" + ); + assert_eq!(sps.ctb_log2_size(), 6, "{name}: 64x64 CTBs"); + assert_eq!(sps.pic_size_in_ctbs(), 9, "{name}: 3x3 CTBs"); + assert!(sps.sps_temporal_mvp_enabled_flag, "{name}: TMVP"); + } + } + + let main = &parse_stream("opengop", OPENGOP).sps[0]; + assert_eq!(main.profile_tier_level.general_profile_idc, 1, "Main"); + assert_eq!(main.bit_depth_luma_minus8, 0); + assert!(!main.conformance_window_flag); + assert_eq!(main.display_dimensions(), (176, 144)); + assert!(main.sample_adaptive_offset_enabled_flag); + assert!(main.strong_intra_smoothing_enabled_flag); + assert!(!main.amp_enabled_flag); + assert_eq!(main.max_latency_pictures(), Some(5)); + let vui = main.vui.as_ref().expect("x265 writes a VUI"); + assert_eq!(vui.aspect_ratio_idc, 1); + assert_eq!(vui.video_format, 5); + assert_eq!((vui.vui_num_units_in_tick, vui.vui_time_scale), (1, 30)); + + // Main 10 at 172x140: coded 176x144 with a 2-sample (4 luma) crop. + let main10 = &parse_stream("main10", MAIN10).sps[0]; + assert_eq!(main10.profile_tier_level.general_profile_idc, 2); + assert_eq!( + (main10.bit_depth_luma_minus8, main10.bit_depth_chroma_minus8), + (2, 2) + ); + assert!(main10.conformance_window_flag); + assert_eq!( + (main10.conf_win_right_offset, main10.conf_win_bottom_offset), + (2, 2) + ); + assert_eq!(main10.display_dimensions(), (172, 140)); + + // Three temporal sub-layers; every one signals the same DPB sizes. + let parsed = parse_stream("temporal", TEMPORAL); + let (sps, vps) = (&parsed.sps[0], &parsed.vps[0]); + assert_eq!(sps.sps_max_sub_layers_minus1, 2); + assert_eq!(sps.highest_tid(), 2); + assert!(!sps.sps_temporal_id_nesting_flag && !vps.vps_temporal_id_nesting_flag); + assert!(vps.vps_sub_layer_ordering_info_present_flag); + assert_eq!( + vps.dec_pic_buf_mgr.max_dec_pic_buffering_minus1[..3], + [4, 4, 4] + ); + assert_eq!(vps.dec_pic_buf_mgr.max_num_reorder_pics[..3], [2, 2, 2]); + assert_eq!( + vps.dec_pic_buf_mgr.max_latency_increase_plus1[..3], + [4, 4, 4] + ); + assert_eq!(sps.reorder_depth(), 2); +} + +/// NVENC's SPS: one short-term set, and a VUI with HRD parameters that must be +/// skipped exactly to reach the bitstream restriction and extension flags. +#[test] +fn parses_nvenc_sps_rps_and_hrd() { + let parsed = parse_stream("nvenc", NVENC); + let sps = &parsed.sps[0]; + // 32x32 CTBs, coded at 192x160 and cropped by 8 chroma samples a side. + assert_eq!( + ( + sps.pic_width_in_luma_samples, + sps.pic_height_in_luma_samples + ), + (192, 160) + ); + assert_eq!( + (sps.conf_win_right_offset, sps.conf_win_bottom_offset), + (8, 8) + ); + assert_eq!(sps.ctb_log2_size(), 5); + assert_eq!(sps.pic_size_in_ctbs(), 30); + assert_eq!(sps.max_dec_pic_buffering(), 6); + assert_eq!(sps.max_latency_pictures(), None); + assert!(sps.amp_enabled_flag && !sps.sps_temporal_mvp_enabled_flag); + assert_eq!(sps.profile_tier_level.general_level_idc, 90); + assert_eq!(sps.num_short_term_ref_pic_sets(), 1); + let set = &sps.short_term_ref_pic_sets[0]; + assert!(!set.syntax.inter_ref_pic_set_prediction_flag); + assert_eq!(set.syntax.num_negative_pics, 5); + assert_eq!(set.syntax.num_positive_pics, 0); + assert_eq!(set.syntax.used_by_curr_pic_s0_flag, 0b00011); + assert_eq!(set.rps.delta_poc_s0, vec![-1, -2, -3, -4, -5]); + assert_eq!(set.rps.used_s0, vec![true, true, false, false, false]); + + let vui = sps.vui.as_ref().unwrap(); + assert!(vui.vui_hrd_parameters_present_flag); + assert!(!sps.sps_extension_present_flag); + + // Each slice codes its own set at index 1, so the inter prediction flag + // is coded (as 0) and counts towards NumBitsForSTRefPicSetInSlice. + for s in parsed.pictures().filter(|s| !s.nal.nal_type().is_idr()) { + let inline = s.header.st_ref_pic_set.as_ref().expect("inline set"); + assert!(!inline.syntax.inter_ref_pic_set_prediction_flag); + assert_eq!(s.header.num_delta_pocs_of_ref_rps_idx(sps), 0); + } +} + +/// PPS values against trace_headers. +#[test] +fn parses_pps() { + for &(name, data, _, _) in ALL { + let pps = &parse_stream(name, data).pps[0]; + assert_eq!(pps.pps_pic_parameter_set_id, 0, "{name}"); + assert_eq!(pps.pps_seq_parameter_set_id, 0, "{name}"); + assert!(!pps.tiles_enabled_flag, "{name}: x265 has no tiles"); + assert_eq!(pps.tile_column_widths(3), vec![3], "{name}"); + assert!(!pps.lists_modification_present_flag, "{name}"); + assert!(!pps.pps_extension_present_flag, "{name}"); + } + let x265 = &parse_stream("opengop", OPENGOP).pps[0]; + assert!( + x265.entropy_coding_sync_enabled_flag, + "WPP is x265's default" + ); + assert!(x265.sign_data_hiding_enabled_flag); + assert!(x265.cu_qp_delta_enabled_flag); + assert_eq!(x265.diff_cu_qp_delta_depth, 1); + assert!(x265.weighted_pred_flag); + assert!(!x265.weighted_bipred_flag); + assert!(x265.pps_loop_filter_across_slices_enabled_flag); + assert!(!x265.deblocking_filter_control_present_flag); + assert_eq!(x265.log2_parallel_merge_level_minus2, 0); + let closed = &parse_stream("closedgop", CLOSEDGOP).pps[0]; + assert!(!closed.entropy_coding_sync_enabled_flag, "wpp=0"); +} + +/// Explicit SPS scaling lists, against the values rebuilt from the trace's +/// `scaling_list_delta_coef`s, including lists predicted from the previous +/// matrix and the DC coefficients of the 16x16 and 32x32 lists. +#[test] +fn parses_scaling_lists() { + let sps = &parse_stream("scaling", SCALING).sps[0]; + assert!(sps.scaling_list_enabled_flag && sps.sps_scaling_list_data_present_flag); + let lists = sps.scaling_lists.as_ref().unwrap(); + // Coded order: the HM file's raster 4x4 starts 12, 14, 19, 24 / 22, ... + // and the up-right diagonal scan visits (0,0), (1,0), (0,1), (2,0), ... + assert_eq!( + lists.scaling_list_4x4[0][..8], + [12, 22, 14, 36, 25, 19, 18, 41] + ); + assert_eq!( + lists.scaling_list_8x8[0][..8], + [19, 44, 21, 40, 46, 27, 34, 38] + ); + assert_eq!( + lists.scaling_list_16x16[0][..8], + [22, 50, 27, 44, 49, 33, 40, 48] + ); + assert_eq!( + lists.scaling_list_32x32[1][..8], + [32, 53, 35, 49, 58, 35, 45, 52] + ); + let sums: Vec = (0..6) + .map(|m| lists.scaling_list_16x16[m].iter().map(|&c| c as u32).sum()) + .collect(); + assert_eq!(sums, vec![2424, 2476, 2537, 2592, 2708, 2708]); + // matrixId 5 is predicted from 4 at every size (delta 1). + assert_eq!(lists.scaling_list_4x4[5], lists.scaling_list_4x4[4]); + assert_eq!(lists.scaling_list_8x8[5], lists.scaling_list_8x8[4]); + assert_eq!(lists.scaling_list_16x16[5], lists.scaling_list_16x16[4]); + assert_eq!(lists.scaling_list_dc_coef_16x16, [23, 27, 27, 31, 28, 28]); + assert_eq!(lists.scaling_list_dc_coef_32x32, [32, 33]); + + // scaling-list=default: enabled, no data, so the Table 7-6 defaults. + let sps = &parse_stream("temporal", TEMPORAL).sps[0]; + assert!(sps.scaling_list_enabled_flag && !sps.sps_scaling_list_data_present_flag); + let defaults = sps.scaling_lists.as_ref().unwrap(); + assert_eq!(defaults.scaling_list_4x4[0], [16; 16]); + assert_eq!(defaults.scaling_list_8x8[0][63], 115, "intra default"); + assert_eq!(defaults.scaling_list_32x32[1][63], 91, "inter default"); + assert_eq!(defaults.scaling_list_dc_coef_32x32, [16, 16]); +} + +/// Per picture, in decoding order: nal_unit_type, slice_pic_order_cnt_lsb, +/// and the bits of the inline st_ref_pic_set() (from trace_headers' bit +/// positions: the element after short_term_ref_pic_set_sps_flag up to +/// slice_temporal_mvp_enabled_flag). +#[test] +fn slice_headers_match_trace() { + /// Name, stream, nal_unit_type, POC LSB and RPS bits per picture. + type Case<'a> = (&'a str, &'a [u8], &'a [u8], &'a [u32], &'a [u32]); + let cases: &[Case] = &[ + ( + "opengop", + OPENGOP, + &[ + 20, 1, 1, 1, 0, 0, 1, 1, 0, 0, 21, 9, 8, 1, 1, 0, 1, 1, 0, 1, 1, 0, 21, 9, 8, 1, 1, + 0, 0, 1, + ], + &[ + 0, 1, 5, 3, 2, 4, 9, 7, 6, 8, 12, 11, 10, 15, 14, 13, 18, 17, 16, 21, 20, 19, 24, + 23, 22, 28, 26, 25, 27, 29, + ], + &[ + 0, 6, 12, 16, 16, 18, 22, 24, 18, 20, 22, 24, 16, 8, 12, 12, 16, 20, 14, 18, 20, + 14, 18, 20, 14, 10, 14, 14, 14, 16, + ], + ), + ( + "closedgop", + CLOSEDGOP, + &[ + 20, 1, 1, 1, 0, 0, 1, 0, 19, 7, 6, 1, 1, 0, 1, 0, 1, 0, 19, 7, 6, 1, 1, 0, 1, 0, 1, + 0, 1, 0, + ], + &[ + 0, 1, 5, 3, 2, 4, 7, 6, 0, 255, 254, 3, 2, 1, 5, 4, 7, 6, 0, 255, 254, 3, 2, 1, 5, + 4, 7, 6, 9, 8, + ], + &[ + 0, 6, 12, 16, 16, 18, 20, 20, 0, 6, 8, 8, 12, 12, 16, 18, 20, 18, 0, 6, 8, 8, 12, + 12, 16, 18, 20, 18, 20, 20, + ], + ), + ( + "nvenc", + NVENC, + &[19, 1, 1, 0, 0, 1, 1, 0, 0, 1, 19, 1, 1, 0, 0, 1, 1, 0, 0, 1], + &[0, 4, 2, 1, 3, 8, 6, 5, 7, 9, 0, 4, 2, 1, 3, 8, 6, 5, 7, 9], + &[ + 0, 11, 15, 15, 15, 21, 25, 25, 25, 25, 0, 11, 15, 15, 15, 21, 25, 25, 25, 25, + ], + ), + ( + "temporal", + TEMPORAL, + &[20, 1, 3, 2, 2, 1, 3, 2, 2, 1, 3, 2, 2, 1, 3, 2, 2, 1, 1, 2], + &[ + 0, 4, 2, 1, 3, 8, 6, 5, 7, 12, 10, 9, 11, 16, 14, 13, 15, 19, 18, 17, + ], + &[ + 0, 10, 14, 14, 14, 16, 20, 20, 22, 24, 28, 20, 22, 24, 28, 20, 22, 22, 26, 18, + ], + ), + ]; + for &(name, data, types, lsbs, bits) in cases { + let parsed = parse_stream(name, data); + let pictures: Vec<_> = parsed.pictures().collect(); + let got: Vec = pictures.iter().map(|s| s.nal.nal_type().raw()).collect(); + assert_eq!(got, types, "{name}: nal_unit_type"); + let got: Vec = pictures + .iter() + .map(|s| s.header.slice_pic_order_cnt_lsb) + .collect(); + assert_eq!(got, lsbs, "{name}: slice_pic_order_cnt_lsb"); + let got: Vec = pictures + .iter() + .map(|s| s.header.num_bits_for_st_ref_pic_set) + .collect(); + assert_eq!(got, bits, "{name}: NumBitsForSTRefPicSetInSlice"); + for s in &pictures { + assert!( + s.header.pic_output_flag, + "{name}: no output_flag_present_flag" + ); + assert!( + !s.header.short_term_ref_pic_set_sps_flag, + "{name}: every encoder here codes the set inline" + ); + } + } + + // Temporal sub-layers: TemporalId per picture, and TSA types. + let parsed = parse_stream("temporal", TEMPORAL); + let tids: Vec = parsed + .pictures() + .map(|s| s.nal.header.temporal_id) + .collect(); + assert_eq!( + tids, + vec![0, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1] + ); + let types: Vec = parsed.pictures().map(|s| s.nal.nal_type()).collect(); + assert_eq!(types[2], NalType::TsaR); + assert_eq!(types[3], NalType::TsaN); +} + +/// POC in decoding order, derived by the DPB, against the trace's LSBs and +/// IRAP structure. closedgop's RADL pictures have LSB 255/254 after an IDR, +/// which is POC -1/-2. +#[test] +fn poc_sequences() { + let cases: &[(&str, &[u8], &[i32])] = &[ + ( + "opengop", + OPENGOP, + &[ + 0, 1, 5, 3, 2, 4, 9, 7, 6, 8, 12, 11, 10, 15, 14, 13, 18, 17, 16, 21, 20, 19, 24, + 23, 22, 28, 26, 25, 27, 29, + ], + ), + ( + "closedgop", + CLOSEDGOP, + &[ + 0, 1, 5, 3, 2, 4, 7, 6, 0, -1, -2, 3, 2, 1, 5, 4, 7, 6, 0, -1, -2, 3, 2, 1, 5, 4, + 7, 6, 9, 8, + ], + ), + ( + "nvenc", + NVENC, + &[0, 4, 2, 1, 3, 8, 6, 5, 7, 9, 0, 4, 2, 1, 3, 8, 6, 5, 7, 9], + ), + ( + "temporal", + TEMPORAL, + &[ + 0, 4, 2, 1, 3, 8, 6, 5, 7, 12, 10, 9, 11, 16, 14, 13, 15, 19, 18, 17, + ], + ), + ]; + for &(name, data, expected) in cases { + let outcomes = run_dpb(name, &parse_stream(name, data)); + let pocs: Vec = outcomes.iter().map(|o| o.poc.unwrap()).collect(); + assert_eq!(pocs, expected, "{name}: POC in decoding order"); + } + + // In every fixture, each coded video sequence covers a contiguous range of + // POCs, which is what a display-order sort of the output needs. + for &(name, data, _, output) in ALL { + let outcomes = run_dpb(name, &parse_stream(name, data)); + let decoded: Vec<_> = outcomes.iter().filter(|o| o.poc.is_some()).collect(); + assert_eq!(decoded.len(), output, "{name}: pictures output, as ffprobe"); + let mut sequences: Vec> = Vec::new(); + for o in decoded { + if o.is_keyframe { + sequences.push(Vec::new()); + } + sequences.last_mut().unwrap().push(o.poc.unwrap()); + } + for mut pocs in sequences { + pocs.sort(); + assert!( + pocs.windows(2).all(|w| w[1] == w[0] + 1), + "{name}: contiguous POCs {pocs:?}" + ); + } + } +} + +/// Every reference the slice headers name exists, in a DPB no larger than the +/// SPS allows, except where random access left them behind. +#[test] +fn reference_picture_sets_resolve() { + for &(name, data, _, _) in ALL { + let outcomes = run_dpb(name, &parse_stream(name, data)); + for (i, o) in outcomes.iter().enumerate() { + if o.skip.is_some() { + continue; + } + assert!( + !o.missing_reference, + "{name}: picture {i} misses a reference" + ); + let entries = o + .rps + .st_curr_before + .iter() + .chain(&o.rps.st_curr_after) + .chain(&o.rps.st_foll); + for e in entries { + if name == "cra_start" && i == 0 { + // The CRA keeps pictures for its RASL pictures, which + // this cut does not have. + assert!(e.slot.is_none(), "{name}: nothing before the CRA"); + } else { + assert!(e.slot.is_some(), "{name}: picture {i}: POC {} held", e.poc); + } + } + if o.nal_type.is_irap() { + assert!( + o.rps.st_curr_before.is_empty() && o.rps.st_curr_after.is_empty(), + "{name}: an IRAP predicts from nothing" + ); + } + // B pictures reference both directions, P pictures only the past. + let b = o.rps.st_curr_after.iter().all(|e| e.poc > o.poc.unwrap()); + let p = o.rps.st_curr_before.iter().all(|e| e.poc < o.poc.unwrap()); + assert!(b && p, "{name}: picture {i} list ordering"); + } + } +} + +/// A stream cut at a CRA: the CRA starts decoding, its two RASL pictures are +/// skipped, and everything after it decodes from the CRA alone. +#[test] +fn cra_start_skips_rasl() { + let outcomes = run_dpb("cra_start", &parse_stream("cra_start", CRA_START)); + let summary: Vec<(NalType, Option, Option)> = outcomes + .iter() + .map(|o| (o.nal_type, o.poc, o.skip)) + .collect(); + let rasl = Some(SkipReason::RaslWithoutReferences); + assert_eq!( + summary[..4], + [ + (NalType::Cra, Some(24), None), + (NalType::RaslR, None, rasl), + (NalType::RaslN, None, rasl), + (NalType::TrailR, Some(28), None), + ] + ); + assert!(outcomes[0].is_keyframe); + // POC 28 is a P picture predicting from the CRA only. + assert_eq!(outcomes[3].rps.st_curr_before.len(), 1); + assert_eq!(outcomes[3].rps.st_curr_before[0].poc, 24); + + // In the uncut stream the same CRA is not a keyframe, and its RASL + // pictures decode. + let outcomes = run_dpb("opengop", &parse_stream("opengop", OPENGOP)); + let cra = outcomes.iter().position(|o| o.poc == Some(24)).unwrap(); + assert_eq!(outcomes[cra].nal_type, NalType::Cra); + assert!(!outcomes[cra].is_keyframe); + assert_eq!(outcomes[cra + 1].poc, Some(23)); + assert!(!outcomes[cra + 1].missing_reference); +} diff --git a/src/decoder/h265/pps.rs b/src/decoder/h265/pps.rs new file mode 100644 index 00000000..1cc5c385 --- /dev/null +++ b/src/decoder/h265/pps.rs @@ -0,0 +1,243 @@ +//! The H.265 picture parameter set (clause 7.3.2.3). +//! +//! Field names follow the standard, which are also the names +//! `StdVideoH265PictureParameterSet` uses. + +use super::parser::Reader; +use super::scaling::{ScalingLists, parse_scaling_list_data}; +use crate::error::Result; + +/// Tile columns `StdVideoH265PictureParameterSet::column_width_minus1` holds. +pub(crate) const MAX_TILE_COLUMNS: usize = 20; +/// Tile rows `StdVideoH265PictureParameterSet::row_height_minus1` holds. +pub(crate) const MAX_TILE_ROWS: usize = 22; + +/// `pps_range_extension()` (clause 7.3.2.3.2). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct PpsRangeExtension { + pub log2_max_transform_skip_block_size_minus2: u8, + pub cross_component_prediction_enabled_flag: bool, + pub chroma_qp_offset_list_enabled_flag: bool, + pub diff_cu_chroma_qp_offset_depth: u8, + pub chroma_qp_offset_list_len_minus1: u8, + pub cb_qp_offset_list: [i8; 6], + pub cr_qp_offset_list: [i8; 6], + pub log2_sao_offset_scale_luma: u8, + pub log2_sao_offset_scale_chroma: u8, +} + +/// Parsed H.265 picture parameter set. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct Pps { + pub pps_pic_parameter_set_id: u8, + pub pps_seq_parameter_set_id: u8, + pub dependent_slice_segments_enabled_flag: bool, + pub output_flag_present_flag: bool, + pub num_extra_slice_header_bits: u8, + pub sign_data_hiding_enabled_flag: bool, + pub cabac_init_present_flag: bool, + pub num_ref_idx_l0_default_active_minus1: u8, + pub num_ref_idx_l1_default_active_minus1: u8, + pub init_qp_minus26: i8, + pub constrained_intra_pred_flag: bool, + pub transform_skip_enabled_flag: bool, + pub cu_qp_delta_enabled_flag: bool, + pub diff_cu_qp_delta_depth: u8, + pub pps_cb_qp_offset: i8, + pub pps_cr_qp_offset: i8, + pub pps_slice_chroma_qp_offsets_present_flag: bool, + pub weighted_pred_flag: bool, + pub weighted_bipred_flag: bool, + pub transquant_bypass_enabled_flag: bool, + pub tiles_enabled_flag: bool, + pub entropy_coding_sync_enabled_flag: bool, + pub num_tile_columns_minus1: u8, + pub num_tile_rows_minus1: u8, + pub uniform_spacing_flag: bool, + /// `column_width_minus1[i]`, coded only without uniform spacing; see + /// `Pps::tile_column_widths` for the widths either way. + pub column_width_minus1: Vec, + /// `row_height_minus1[i]`, coded only without uniform spacing. + pub row_height_minus1: Vec, + pub loop_filter_across_tiles_enabled_flag: bool, + pub pps_loop_filter_across_slices_enabled_flag: bool, + pub deblocking_filter_control_present_flag: bool, + pub deblocking_filter_override_enabled_flag: bool, + pub pps_deblocking_filter_disabled_flag: bool, + pub pps_beta_offset_div2: i8, + pub pps_tc_offset_div2: i8, + pub pps_scaling_list_data_present_flag: bool, + /// The coded lists when `pps_scaling_list_data_present_flag` is set; they + /// replace the SPS's for pictures using this PPS. + pub scaling_lists: Option, + pub lists_modification_present_flag: bool, + pub log2_parallel_merge_level_minus2: u8, + pub slice_segment_header_extension_present_flag: bool, + pub pps_extension_present_flag: bool, + pub pps_range_extension_flag: bool, + pub pps_multilayer_extension_flag: bool, + pub pps_3d_extension_flag: bool, + pub pps_scc_extension_flag: bool, + pub pps_extension_4bits: u8, + pub range_extension: PpsRangeExtension, +} + +// The driver takes the coded tile syntax; the derived sizes are for tests. +#[cfg(test)] +impl Pps { + /// Tile column widths in CTBs (equation 6-3), for a picture + /// `pic_width_in_ctbs` wide. One column when tiles are off. + pub fn tile_column_widths(&self, pic_width_in_ctbs: u32) -> Vec { + tile_sizes( + self.tiles_enabled_flag, + self.uniform_spacing_flag, + self.num_tile_columns_minus1 as u32, + &self.column_width_minus1, + pic_width_in_ctbs, + ) + } +} + +/// Equations 6-3 and 6-4, which have the same shape for columns and rows. +#[cfg(test)] +fn tile_sizes( + tiles: bool, + uniform: bool, + count_minus1: u32, + explicit_minus1: &[u16], + total: u32, +) -> Vec { + if !tiles { + return vec![total]; + } + let count = count_minus1 + 1; + if uniform { + return (0..count) + .map(|i| ((i + 1) * total) / count - (i * total) / count) + .collect(); + } + let mut sizes: Vec = explicit_minus1.iter().map(|w| *w as u32 + 1).collect(); + let used: u32 = sizes.iter().sum(); + // The last one takes whatever is left. + sizes.push(total.saturating_sub(used)); + sizes +} + +/// Parse a PPS NAL payload (EBSP, after the 2-byte NAL header). +pub(crate) fn parse_pps(payload: &[u8]) -> Result { + let mut r = Reader::new(payload); + let mut pps = Pps { + pps_pic_parameter_set_id: r.ue_max(63, "pps_pic_parameter_set_id")? as u8, + pps_seq_parameter_set_id: r.ue_max(15, "pps_seq_parameter_set_id")? as u8, + dependent_slice_segments_enabled_flag: r.flag()?, + output_flag_present_flag: r.flag()?, + num_extra_slice_header_bits: r.bits(3)? as u8, + sign_data_hiding_enabled_flag: r.flag()?, + cabac_init_present_flag: r.flag()?, + ..Default::default() + }; + pps.num_ref_idx_l0_default_active_minus1 = + r.ue_max(14, "num_ref_idx_l0_default_active_minus1")? as u8; + pps.num_ref_idx_l1_default_active_minus1 = + r.ue_max(14, "num_ref_idx_l1_default_active_minus1")? as u8; + // The lower bound depends on the bit depth (-(26 + QpBdOffsetY)), which + // the PPS cannot see; -26 - 48 covers 16-bit video. + pps.init_qp_minus26 = r.se_range(-74, 25, "init_qp_minus26")? as i8; + pps.constrained_intra_pred_flag = r.flag()?; + pps.transform_skip_enabled_flag = r.flag()?; + pps.cu_qp_delta_enabled_flag = r.flag()?; + if pps.cu_qp_delta_enabled_flag { + pps.diff_cu_qp_delta_depth = r.ue_max(3, "diff_cu_qp_delta_depth")? as u8; + } + pps.pps_cb_qp_offset = r.se_range(-12, 12, "pps_cb_qp_offset")? as i8; + pps.pps_cr_qp_offset = r.se_range(-12, 12, "pps_cr_qp_offset")? as i8; + pps.pps_slice_chroma_qp_offsets_present_flag = r.flag()?; + pps.weighted_pred_flag = r.flag()?; + pps.weighted_bipred_flag = r.flag()?; + pps.transquant_bypass_enabled_flag = r.flag()?; + pps.tiles_enabled_flag = r.flag()?; + pps.entropy_coding_sync_enabled_flag = r.flag()?; + + if pps.tiles_enabled_flag { + pps.num_tile_columns_minus1 = + r.ue_max(MAX_TILE_COLUMNS as u32 - 1, "num_tile_columns_minus1")? as u8; + pps.num_tile_rows_minus1 = + r.ue_max(MAX_TILE_ROWS as u32 - 1, "num_tile_rows_minus1")? as u8; + pps.uniform_spacing_flag = r.flag()?; + if !pps.uniform_spacing_flag { + for _ in 0..pps.num_tile_columns_minus1 { + pps.column_width_minus1 + .push(r.ue_max(u16::MAX as u32, "column_width_minus1")? as u16); + } + for _ in 0..pps.num_tile_rows_minus1 { + pps.row_height_minus1 + .push(r.ue_max(u16::MAX as u32, "row_height_minus1")? as u16); + } + } + pps.loop_filter_across_tiles_enabled_flag = r.flag()?; + } + + pps.pps_loop_filter_across_slices_enabled_flag = r.flag()?; + pps.deblocking_filter_control_present_flag = r.flag()?; + if pps.deblocking_filter_control_present_flag { + pps.deblocking_filter_override_enabled_flag = r.flag()?; + pps.pps_deblocking_filter_disabled_flag = r.flag()?; + if !pps.pps_deblocking_filter_disabled_flag { + pps.pps_beta_offset_div2 = r.se_range(-6, 6, "pps_beta_offset_div2")? as i8; + pps.pps_tc_offset_div2 = r.se_range(-6, 6, "pps_tc_offset_div2")? as i8; + } + } + pps.pps_scaling_list_data_present_flag = r.flag()?; + if pps.pps_scaling_list_data_present_flag { + pps.scaling_lists = Some(parse_scaling_list_data(&mut r)?); + } + pps.lists_modification_present_flag = r.flag()?; + pps.log2_parallel_merge_level_minus2 = r.ue_max(4, "log2_parallel_merge_level_minus2")? as u8; + pps.slice_segment_header_extension_present_flag = r.flag()?; + + pps.pps_extension_present_flag = r.flag()?; + if pps.pps_extension_present_flag { + pps.pps_range_extension_flag = r.flag()?; + pps.pps_multilayer_extension_flag = r.flag()?; + pps.pps_3d_extension_flag = r.flag()?; + pps.pps_scc_extension_flag = r.flag()?; + pps.pps_extension_4bits = r.bits(4)? as u8; + } + if pps.pps_range_extension_flag { + let ext = &mut pps.range_extension; + if pps.transform_skip_enabled_flag { + ext.log2_max_transform_skip_block_size_minus2 = + r.ue_max(3, "log2_max_transform_skip_block_size_minus2")? as u8; + } + ext.cross_component_prediction_enabled_flag = r.flag()?; + ext.chroma_qp_offset_list_enabled_flag = r.flag()?; + if ext.chroma_qp_offset_list_enabled_flag { + ext.diff_cu_chroma_qp_offset_depth = + r.ue_max(3, "diff_cu_chroma_qp_offset_depth")? as u8; + ext.chroma_qp_offset_list_len_minus1 = + r.ue_max(5, "chroma_qp_offset_list_len_minus1")? as u8; + for i in 0..=ext.chroma_qp_offset_list_len_minus1 as usize { + ext.cb_qp_offset_list[i] = r.se_range(-12, 12, "cb_qp_offset_list")? as i8; + ext.cr_qp_offset_list[i] = r.se_range(-12, 12, "cr_qp_offset_list")? as i8; + } + } + ext.log2_sao_offset_scale_luma = r.ue_max(6, "log2_sao_offset_scale_luma")? as u8; + ext.log2_sao_offset_scale_chroma = r.ue_max(6, "log2_sao_offset_scale_chroma")? as u8; + } + // As in the SPS, the remaining extensions are recorded by flag only. + Ok(pps) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn uniform_tiles_spread_the_remainder() { + // 10 CTBs over 3 columns: 3, 3, 4 (equation 6-3). + assert_eq!(tile_sizes(true, true, 2, &[], 10), vec![3, 3, 4]); + assert_eq!(tile_sizes(false, false, 0, &[], 10), vec![10]); + // Explicit widths: the last column takes the rest. + assert_eq!(tile_sizes(true, false, 2, &[1, 3], 10), vec![2, 4, 4]); + } +} diff --git a/src/decoder/h265/record.rs b/src/decoder/h265/record.rs new file mode 100644 index 00000000..51ae32fb --- /dev/null +++ b/src/decoder/h265/record.rs @@ -0,0 +1,29 @@ +//! The H.265 structures of a decode command. + +use ash::vk; +use ash::vk::native::{StdVideoDecodeH265PictureInfo, StdVideoDecodeH265ReferenceInfo}; + +use crate::decoder::record::RecordCodec; + +/// H.265's part of a [`DecodeRecord`](crate::decoder::record::DecodeRecord). +pub(super) struct H265Record; + +impl RecordCodec for H265Record { + type RefStd = StdVideoDecodeH265ReferenceInfo; + type DpbSlot = vk::VideoDecodeH265DpbSlotInfoKHR<'static>; + type Picture = StdVideoDecodeH265PictureInfo; + type PictureInfo = vk::VideoDecodeH265PictureInfoKHR<'static>; + + fn dpb_slot(std: &'static StdVideoDecodeH265ReferenceInfo) -> Self::DpbSlot { + vk::VideoDecodeH265DpbSlotInfoKHR::default().std_reference_info(std) + } + + fn picture_info( + picture: &'static mut StdVideoDecodeH265PictureInfo, + offsets: &'static [u32], + ) -> Self::PictureInfo { + vk::VideoDecodeH265PictureInfoKHR::default() + .std_picture_info(picture) + .slice_segment_offsets(offsets) + } +} diff --git a/src/decoder/h265/rps.rs b/src/decoder/h265/rps.rs new file mode 100644 index 00000000..622e23f9 --- /dev/null +++ b/src/decoder/h265/rps.rs @@ -0,0 +1,263 @@ +//! `st_ref_pic_set()` (clause 7.3.7) and its semantics (clause 7.4.8). +//! +//! Each set is kept twice: as the syntax that was coded, which is what +//! `StdVideoH265ShortTermRefPicSet` carries, and as the derived +//! `DeltaPocS0`/`DeltaPocS1`/`UsedByCurrPicS0`/`UsedByCurrPicS1` lists, which +//! is what the reference picture set derivation (clause 8.3.2) consumes. With +//! inter RPS prediction the two differ: the syntax is a delta against another +//! set, and only the derived form says which pictures are meant. + +use super::parser::{Reader, invalid}; +use crate::error::Result; + +/// The most entries a short-term set can have (`MaxDpbSize - 1` is at most 15, +/// and Vulkan sizes its arrays at 16). +pub(crate) const MAX_ST_REFS: usize = 16; + +/// `st_ref_pic_set()` as coded. +/// +/// Bit masks hold one flag per entry, bit `j` for entry `j`, as the Vulkan +/// structure does. Only the fields of the branch that was coded are set: the +/// inter prediction fields when `inter_ref_pic_set_prediction_flag` is 1, the +/// explicit lists otherwise. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct StRefPicSetSyntax { + pub inter_ref_pic_set_prediction_flag: bool, + /// Coded only in a slice header; 0 (inferred) in the SPS. + pub delta_idx_minus1: u32, + pub delta_rps_sign: bool, + pub abs_delta_rps_minus1: u32, + /// `used_by_curr_pic_flag[j]` for j in `0..=NumDeltaPocs[RefRpsIdx]`. + /// + /// That can be 17 entries, one more than the `u16` Vulkan uses holds; a + /// set that needs bit 16 cannot be described to Vulkan exactly. + pub used_by_curr_pic_flag: u32, + /// `use_delta_flag[j]`, with the inferred 1s filled in. + pub use_delta_flag: u32, + pub num_negative_pics: u8, + pub num_positive_pics: u8, + pub delta_poc_s0_minus1: [u16; MAX_ST_REFS], + pub delta_poc_s1_minus1: [u16; MAX_ST_REFS], + pub used_by_curr_pic_s0_flag: u16, + pub used_by_curr_pic_s1_flag: u16, +} + +/// The derived form of a short-term reference picture set (equations 7-61 +/// to 7-64): POC deltas relative to the current picture, closest first. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct StRps { + /// `DeltaPocS0`, all negative, in decreasing order. + pub delta_poc_s0: Vec, + /// `UsedByCurrPicS0`. + pub used_s0: Vec, + /// `DeltaPocS1`, all positive, in increasing order. + pub delta_poc_s1: Vec, + /// `UsedByCurrPicS1`. + pub used_s1: Vec, +} + +impl StRps { + pub fn num_negative_pics(&self) -> usize { + self.delta_poc_s0.len() + } + + pub fn num_positive_pics(&self) -> usize { + self.delta_poc_s1.len() + } + + /// `NumDeltaPocs`. + pub fn num_delta_pocs(&self) -> usize { + self.delta_poc_s0.len() + self.delta_poc_s1.len() + } +} + +/// One short-term reference picture set, coded and derived. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct StRefPicSet { + pub syntax: StRefPicSetSyntax, + pub rps: StRps, + /// `RefRpsIdx` when inter predicted, which Vulkan's + /// `NumDeltaPocsOfRefRpsIdx` is derived from. + pub ref_rps_idx: Option, +} + +/// Parse `st_ref_pic_set(st_rps_idx)`. +/// +/// `previous` holds the sets already parsed from the SPS (all of them, when +/// this is the set in a slice header, where `st_rps_idx` equals +/// `num_short_term_ref_pic_sets`). `max_refs` bounds the set's size; it is +/// `sps_max_dec_pic_buffering_minus1` of the highest sub-layer. +pub(crate) fn parse_st_ref_pic_set( + r: &mut Reader, + st_rps_idx: usize, + num_short_term_ref_pic_sets: usize, + previous: &[StRefPicSet], + max_refs: usize, +) -> Result { + let mut set = StRefPicSet::default(); + let syntax = &mut set.syntax; + if st_rps_idx != 0 { + syntax.inter_ref_pic_set_prediction_flag = r.flag()?; + } + + if syntax.inter_ref_pic_set_prediction_flag { + if st_rps_idx == num_short_term_ref_pic_sets { + syntax.delta_idx_minus1 = r.ue_max(st_rps_idx as u32 - 1, "delta_idx_minus1")?; + } + syntax.delta_rps_sign = r.flag()?; + syntax.abs_delta_rps_minus1 = r.ue_max(32767, "abs_delta_rps_minus1")?; + let ref_rps_idx = st_rps_idx - (syntax.delta_idx_minus1 as usize + 1); + let reference = &previous + .get(ref_rps_idx) + .ok_or_else(|| invalid("st_ref_pic_set references a missing set"))? + .rps; + set.ref_rps_idx = Some(ref_rps_idx); + + let num_delta_pocs = reference.num_delta_pocs(); + for j in 0..=num_delta_pocs { + let used = r.flag()?; + // use_delta_flag is inferred to be 1 when not present. + let use_delta = used || r.flag()?; + syntax.used_by_curr_pic_flag |= (used as u32) << j; + syntax.use_delta_flag |= (use_delta as u32) << j; + } + let delta_rps = + (1 - 2 * syntax.delta_rps_sign as i32) * (syntax.abs_delta_rps_minus1 as i32 + 1); + set.rps = predict_rps( + reference, + delta_rps, + syntax.used_by_curr_pic_flag, + syntax.use_delta_flag, + ); + if set.rps.num_delta_pocs() > max_refs.min(MAX_ST_REFS) { + return Err(invalid("predicted st_ref_pic_set has too many entries")); + } + return Ok(set); + } + + let limit = max_refs.min(MAX_ST_REFS) as u32; + syntax.num_negative_pics = r.ue_max(limit, "num_negative_pics")? as u8; + syntax.num_positive_pics = + r.ue_max(limit - syntax.num_negative_pics as u32, "num_positive_pics")? as u8; + + let mut poc = 0i32; + for i in 0..syntax.num_negative_pics as usize { + let minus1 = r.ue_max(32767, "delta_poc_s0_minus1")?; + let used = r.flag()?; + syntax.delta_poc_s0_minus1[i] = minus1 as u16; + syntax.used_by_curr_pic_s0_flag |= (used as u16) << i; + poc -= minus1 as i32 + 1; + set.rps.delta_poc_s0.push(poc); + set.rps.used_s0.push(used); + } + poc = 0; + for i in 0..syntax.num_positive_pics as usize { + let minus1 = r.ue_max(32767, "delta_poc_s1_minus1")?; + let used = r.flag()?; + syntax.delta_poc_s1_minus1[i] = minus1 as u16; + syntax.used_by_curr_pic_s1_flag |= (used as u16) << i; + poc += minus1 as i32 + 1; + set.rps.delta_poc_s1.push(poc); + set.rps.used_s1.push(used); + } + Ok(set) +} + +/// Equations 7-61 and 7-62: derive a set from `reference` shifted by +/// `delta_rps`, keeping the entries `use_delta` selects. +/// +/// The flags are indexed as the syntax indexes them: the reference's negative +/// entries first, then its positive entries, then one for the reference +/// picture itself (at `NumDeltaPocs[RefRpsIdx]`). +fn predict_rps(reference: &StRps, delta_rps: i32, used: u32, use_delta: u32) -> StRps { + let flag = |mask: u32, j: usize| mask & (1 << j) != 0; + let num_negative = reference.num_negative_pics(); + let num_delta = reference.num_delta_pocs(); + let mut out = StRps::default(); + + // Negative pictures, closest first. + for j in (0..reference.num_positive_pics()).rev() { + let d_poc = reference.delta_poc_s1[j] + delta_rps; + if d_poc < 0 && flag(use_delta, num_negative + j) { + out.delta_poc_s0.push(d_poc); + out.used_s0.push(flag(used, num_negative + j)); + } + } + if delta_rps < 0 && flag(use_delta, num_delta) { + out.delta_poc_s0.push(delta_rps); + out.used_s0.push(flag(used, num_delta)); + } + for j in 0..num_negative { + let d_poc = reference.delta_poc_s0[j] + delta_rps; + if d_poc < 0 && flag(use_delta, j) { + out.delta_poc_s0.push(d_poc); + out.used_s0.push(flag(used, j)); + } + } + + // Positive pictures, closest first. + for j in (0..num_negative).rev() { + let d_poc = reference.delta_poc_s0[j] + delta_rps; + if d_poc > 0 && flag(use_delta, j) { + out.delta_poc_s1.push(d_poc); + out.used_s1.push(flag(used, j)); + } + } + if delta_rps > 0 && flag(use_delta, num_delta) { + out.delta_poc_s1.push(delta_rps); + out.used_s1.push(flag(used, num_delta)); + } + for j in 0..reference.num_positive_pics() { + let d_poc = reference.delta_poc_s1[j] + delta_rps; + if d_poc > 0 && flag(use_delta, num_negative + j) { + out.delta_poc_s1.push(d_poc); + out.used_s1.push(flag(used, num_negative + j)); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn rps(s0: &[(i32, bool)], s1: &[(i32, bool)]) -> StRps { + StRps { + delta_poc_s0: s0.iter().map(|e| e.0).collect(), + used_s0: s0.iter().map(|e| e.1).collect(), + delta_poc_s1: s1.iter().map(|e| e.0).collect(), + used_s1: s1.iter().map(|e| e.1).collect(), + } + } + + /// The reference set {-1, +1, +3} predicted with deltaRps = -2 gives + /// {-1 - 2, 1 - 2, 3 - 2} plus the reference picture itself at -2, which + /// sorted closest first is S0 = {-1, -2, -3}, S1 = {+1}. + #[test] + fn inter_prediction_shifts_and_resorts() { + let reference = rps(&[(-1, true)], &[(1, true), (3, true)]); + // Four flags: S0[0], S1[0], S1[1], then the reference picture. + let all = 0b1111; + let out = predict_rps(&reference, -2, all, all); + assert_eq!(out.delta_poc_s0, vec![-1, -2, -3]); + assert_eq!(out.delta_poc_s1, vec![1]); + assert!(out.used_s0.iter().chain(&out.used_s1).all(|u| *u)); + } + + /// use_delta_flag drops entries, and used_by_curr_pic_flag follows each + /// entry to its new position. + #[test] + fn inter_prediction_honours_flags() { + let reference = rps(&[(-1, true), (-3, true)], &[(2, true)]); + // deltaRps = +1: S0[0] -> 0 (dropped, never 0), S0[1] -> -2, + // S1[0] -> 3, the reference picture -> +1. + // Drop S1[0] with use_delta_flag; mark the reference picture unused. + let use_delta = 0b1011; + let used = 0b0011; + let out = predict_rps(&reference, 1, used, use_delta); + assert_eq!(out.delta_poc_s0, vec![-2]); + assert_eq!(out.used_s0, vec![true]); + assert_eq!(out.delta_poc_s1, vec![1]); + assert_eq!(out.used_s1, vec![false]); + } +} diff --git a/src/decoder/h265/scaling.rs b/src/decoder/h265/scaling.rs new file mode 100644 index 00000000..b469902e --- /dev/null +++ b/src/decoder/h265/scaling.rs @@ -0,0 +1,162 @@ +//! `scaling_list_data()` (clause 7.3.4) and the default scaling lists +//! (clause 7.4.5, Tables 7-5 and 7-6). + +use super::parser::{Reader, invalid}; +use crate::error::Result; + +/// Table 7-6, intra (matrixId 0..=2), in coded (up-right diagonal) order. +const DEFAULT_8X8_INTRA: [u8; 64] = [ + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 17, 16, 17, 18, 17, 18, 18, 17, 18, 21, 19, 20, + 21, 20, 19, 21, 24, 22, 22, 24, 24, 22, 22, 24, 25, 25, 27, 30, 27, 25, 25, 29, 31, 35, 35, 31, + 29, 36, 41, 44, 41, 36, 47, 54, 54, 47, 65, 70, 65, 88, 88, 115, +]; + +/// Table 7-6, inter (matrixId 3..=5), in coded (up-right diagonal) order. +const DEFAULT_8X8_INTER: [u8; 64] = [ + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 20, 20, 20, + 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 28, 28, 28, 28, 28, + 28, 33, 33, 33, 33, 33, 41, 41, 41, 41, 54, 54, 54, 71, 71, 91, +]; + +/// A complete set of scaling lists, laid out like `StdVideoH265ScalingLists`. +/// +/// Coefficients are in coded order (the up-right diagonal scan the bitstream +/// uses), not raster order; that is the order the Vulkan structure carries +/// them in. The 32x32 lists hold matrixId 0 (intra luma) and 3 (inter luma) +/// at indices 0 and 1. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ScalingLists { + pub scaling_list_4x4: [[u8; 16]; 6], + pub scaling_list_8x8: [[u8; 64]; 6], + pub scaling_list_16x16: [[u8; 64]; 6], + pub scaling_list_32x32: [[u8; 64]; 2], + pub scaling_list_dc_coef_16x16: [u8; 6], + pub scaling_list_dc_coef_32x32: [u8; 2], +} + +impl Default for ScalingLists { + /// The default lists of Tables 7-5 and 7-6, which apply when + /// `scaling_list_enabled_flag` is 1 and no list data is coded. + fn default() -> Self { + let mut lists = Self { + scaling_list_4x4: [[16; 16]; 6], + scaling_list_8x8: [[0; 64]; 6], + scaling_list_16x16: [[0; 64]; 6], + scaling_list_32x32: [[0; 64]; 2], + scaling_list_dc_coef_16x16: [16; 6], + scaling_list_dc_coef_32x32: [16; 2], + }; + for size_id in 1..4 { + for matrix_id in matrix_ids(size_id) { + lists.set_default(size_id, matrix_id); + } + } + lists + } +} + +/// The matrixId values coded for `size_id`: all six, or only 0 and 3 for 32x32. +fn matrix_ids(size_id: usize) -> impl Iterator { + (0..6).step_by(if size_id == 3 { 3 } else { 1 }) +} + +impl ScalingLists { + /// The coefficients of list (`size_id`, `matrix_id`). + pub fn list(&self, size_id: usize, matrix_id: usize) -> &[u8] { + match size_id { + 0 => &self.scaling_list_4x4[matrix_id], + 1 => &self.scaling_list_8x8[matrix_id], + 2 => &self.scaling_list_16x16[matrix_id], + _ => &self.scaling_list_32x32[matrix_id / 3], + } + } + + fn list_mut(&mut self, size_id: usize, matrix_id: usize) -> &mut [u8] { + match size_id { + 0 => &mut self.scaling_list_4x4[matrix_id], + 1 => &mut self.scaling_list_8x8[matrix_id], + 2 => &mut self.scaling_list_16x16[matrix_id], + _ => &mut self.scaling_list_32x32[matrix_id / 3], + } + } + + /// The DC coefficient of a 16x16 or 32x32 list (`size_id` 2 or 3). + pub fn dc(&self, size_id: usize, matrix_id: usize) -> u8 { + match size_id { + 2 => self.scaling_list_dc_coef_16x16[matrix_id], + 3 => self.scaling_list_dc_coef_32x32[matrix_id / 3], + _ => 16, + } + } + + fn set_dc(&mut self, size_id: usize, matrix_id: usize, value: u8) { + match size_id { + 2 => self.scaling_list_dc_coef_16x16[matrix_id] = value, + 3 => self.scaling_list_dc_coef_32x32[matrix_id / 3] = value, + _ => {} + } + } + + fn set_default(&mut self, size_id: usize, matrix_id: usize) { + let list = self.list_mut(size_id, matrix_id); + if size_id == 0 { + list.fill(16); + } else if matrix_id < 3 { + list.copy_from_slice(&DEFAULT_8X8_INTRA); + } else { + list.copy_from_slice(&DEFAULT_8X8_INTER); + } + self.set_dc(size_id, matrix_id, 16); + } +} + +/// Parse `scaling_list_data()`. +/// +/// For a list predicted from another (`scaling_list_pred_mode_flag` = 0) the +/// reference's coefficients and DC value are copied, or the default list is +/// used when `scaling_list_pred_matrix_id_delta` is 0, so the result is always +/// fully resolved. +pub(crate) fn parse_scaling_list_data(r: &mut Reader) -> Result { + let mut lists = ScalingLists::default(); + for size_id in 0..4 { + let step = if size_id == 3 { 3 } else { 1 }; + for matrix_id in matrix_ids(size_id) { + let pred_mode_flag = r.flag()?; + if !pred_mode_flag { + let delta = r.ue_max( + (matrix_id / step) as u32, + "scaling_list_pred_matrix_id_delta", + )? as usize; + if delta == 0 { + lists.set_default(size_id, matrix_id); + } else { + let ref_matrix_id = matrix_id - delta * step; + let reference = lists.list(size_id, ref_matrix_id).to_vec(); + lists + .list_mut(size_id, matrix_id) + .copy_from_slice(&reference); + let dc = lists.dc(size_id, ref_matrix_id); + lists.set_dc(size_id, matrix_id, dc); + } + continue; + } + + let mut next_coef = 8i32; + if size_id > 1 { + let dc_minus8 = r.se_range(-7, 247, "scaling_list_dc_coef_minus8")?; + next_coef = dc_minus8 + 8; + lists.set_dc(size_id, matrix_id, next_coef as u8); + } + let list = lists.list_mut(size_id, matrix_id); + for coef in list.iter_mut() { + let delta = r.se_range(-128, 127, "scaling_list_delta_coef")?; + next_coef = (next_coef + delta + 256) % 256; + if next_coef == 0 { + return Err(invalid("scaling list coefficient is 0")); + } + *coef = next_coef as u8; + } + } + } + Ok(lists) +} diff --git a/src/decoder/h265/session.rs b/src/decoder/h265/session.rs new file mode 100644 index 00000000..db29a547 --- /dev/null +++ b/src/decoder/h265/session.rs @@ -0,0 +1,496 @@ +//! The H.265 Vulkan decode session: lazy stream-driven setup and per-picture +//! record/submit. + +use std::collections::HashMap; + +use ash::vk; +use ash::vk::TaggedStructure; +use tracing::debug; + +use super::dpb::{DecodeDpb, PictureDecision, PictureState, RefPicSet, SkipReason}; +use super::parser::{self, NalHeader, NalType, NalUnit, Vps}; +use super::pps::{self, Pps}; +use super::record::H265Record; +use super::slice::{self, SliceHeader}; +use super::sps::{self, Sps}; +use super::std_params::StdParameterSets; +use crate::decoder::DecodeConfig; +use crate::decoder::annexb::START_CODE; +use crate::decoder::common::DecoderCommon; +use crate::decoder::driver::{CodecDecoder, Decoded}; +use crate::decoder::pipeline::DECODE_PIPELINE_DEPTH; +use crate::decoder::plan::StreamNeeds; +use crate::decoder::profile::DecodeProfile; +use crate::decoder::record::{DecodeRecord, LinkParams}; +use crate::encoder::{BitDepth, ChromaFormat}; +use crate::error::{PixelForgeError, Result}; +use crate::vulkan::VideoContext; + +/// H.265 decoder. +/// +/// Owns only what is specific to H.265: the parameter sets seen so far, and the +/// POC / reference picture set state. Everything Vulkan lives in +/// [`DecoderCommon`], and the flow around a picture (framing, keyframes, +/// reordering) is [`StreamDecoder`](crate::decoder::driver::StreamDecoder)'s. +pub(crate) struct H265Decoder { + common: DecoderCommon, + + /// How many DPB slots to reserve beyond the stream's needs, so the caller + /// can hold decoded frames while decoding continues. + output_depth: usize, + + /// Parameter sets seen so far, by id. + vps_map: HashMap, + sps_map: HashMap, + pps_map: HashMap, + + /// The parameter sets the current session parameters were built from, kept + /// whole rather than by id: a stream may resend an id with new content, + /// which is how a resolution change is signalled. + active: Option<(Vps, Sps, Pps)>, + + /// POC and reference picture set state, recreated alongside the session. + dpb: Option, + /// An end of sequence NAL unit arrived after the last picture, so the next + /// one starts a new coded video sequence. + pending_eos: bool, + + /// What each pipeline slot's decode command was recorded from, indexed like + /// the pipeline's slots. + records: Box<[DecodeRecord]>, + + /// `sps_max_num_reorder_pics` of the active SPS's highest sub-layer. + reorder_depth: usize, +} + +/// The slice segments making up one picture, gathered from the input. +pub(crate) struct Picture<'a> { + nal: NalHeader, + /// The header of the picture's first slice segment. + header: SliceHeader, + segments: Vec<&'a [u8]>, + /// An end of sequence NAL unit came before this picture. + after_eos: bool, + /// The parameter sets in effect when this picture was grouped. Captured + /// here for the same reason as in the H.264 decoder: a later parameter set + /// in the same chunk must not reach back. + vps: Vps, + sps: Sps, + pps: Pps, +} + +impl H265Decoder { + pub(crate) fn create(context: VideoContext, config: &DecodeConfig) -> Result { + Ok(Self { + common: DecoderCommon::new(context, config.consumer_queue_family)?, + output_depth: config.output_depth, + vps_map: HashMap::new(), + sps_map: HashMap::new(), + pps_map: HashMap::new(), + active: None, + dpb: None, + pending_eos: false, + // SAFETY: the picture info is plain C data, for which all-zero is + // a valid (if meaningless) value. + records: (0..DECODE_PIPELINE_DEPTH) + .map(|_| DecodeRecord::new(unsafe { std::mem::zeroed() })) + .collect(), + reorder_depth: 0, + }) + } + + /// The decode profile for a parsed SPS. + fn profile_for(sps: &Sps) -> Result<(DecodeProfile, ChromaFormat, BitDepth)> { + let chroma_format = match (sps.chroma_format_idc, sps.separate_colour_plane_flag) { + (1, _) => ChromaFormat::Yuv420, + (2, _) => ChromaFormat::Yuv422, + (3, false) => ChromaFormat::Yuv444, + (idc, separate) => { + return Err(PixelForgeError::InvalidInput(format!( + "H.265 decode: unsupported chroma_format_idc {idc} \ + (separate colour planes: {separate})" + ))); + } + }; + let bit_depth = match (sps.bit_depth_luma_minus8, sps.bit_depth_chroma_minus8) { + (0, 0) => BitDepth::Eight, + (2, 2) => BitDepth::Ten, + (luma, chroma) => { + return Err(PixelForgeError::InvalidInput(format!( + "H.265 decode: unsupported bit depth {}/{} (luma/chroma)", + luma + 8, + chroma + 8 + ))); + } + }; + if sps.sps_scc_extension_flag || sps.sps_multilayer_extension_flag { + return Err(PixelForgeError::InvalidInput( + "H.265 decode: screen content and multi-layer extensions are not supported" + .to_string(), + )); + } + // The Std profile enum uses the coded values. + let profile_idc = sps.profile_tier_level.general_profile_idc as u32; + let profile = DecodeProfile::h265(profile_idc, chroma_format, bit_depth); + Ok((profile, chroma_format, bit_depth)) + } + + /// Create (or recreate) the Vulkan session for the given parameter sets. + fn activate(&mut self, vps: &Vps, sps: &Sps, pps: &Pps) -> Result<()> { + if self.common.session.is_some() + && self + .active + .as_ref() + .is_some_and(|(v, s, p)| v == vps && s == sps && p == pps) + { + return Ok(()); + } + + let (profile, chroma_format, bit_depth) = Self::profile_for(sps)?; + // `sps_max_dec_pic_buffering` counts the current picture too. + let references = sps.max_dec_pic_buffering().saturating_sub(1) as u32; + let reorder_depth = sps.reorder_depth(); + let needs = StreamNeeds { + codec: "H.265", + width: sps.pic_width_in_luma_samples, + height: sps.pic_height_in_luma_samples, + chroma_format, + bit_depth, + references, + active_references: references, + slot_limit: super::dpb::MAX_DPB_SLOTS as u32, + reorder_depth, + output_depth: self.output_depth, + film_grain: false, + }; + let mut h265_caps = vk::VideoDecodeH265CapabilitiesKHR::default(); + let plan = self + .common + .plan_session(profile.info(), &mut h265_caps, &needs)?; + let slot_count = plan.slot_count; + self.common + .create_session(&plan, profile, |common, session| { + Self::build_session_params(common, session, vps, sps, pps) + })?; + + self.active = Some((vps.clone(), sps.clone(), pps.clone())); + self.reorder_depth = reorder_depth; + let pins = self.common.session()?.slot_pins.clone(); + self.dpb = Some(DecodeDpb::new(slot_count, pins)); + Ok(()) + } + + /// Translate the parsed parameter sets into StdVideo structs and create + /// session parameters. + fn build_session_params( + common: &DecoderCommon, + session: vk::VideoSessionKHR, + vps: &Vps, + sps: &Sps, + pps: &Pps, + ) -> Result { + let sets = StdParameterSets::new(vps, sps, pps); + let vps_array = [sets.vps]; + let sps_array = [sets.sps]; + let pps_array = [sets.pps]; + let add_info = vk::VideoDecodeH265SessionParametersAddInfoKHR::default() + .std_vp_ss(&vps_array) + .std_sp_ss(&sps_array) + .std_pp_ss(&pps_array); + let mut h265_create = vk::VideoDecodeH265SessionParametersCreateInfoKHR::default() + .max_std_vps_count(1) + .max_std_sps_count(1) + .max_std_pps_count(1) + .parameters_add_info(&add_info); + let create_info = vk::VideoSessionParametersCreateInfoKHR::default() + .video_session(session) + .push(&mut h265_create); + + unsafe { + common + .video_queue_fn + .create_video_session_parameters(&create_info, None) + } + .map_err(PixelForgeError::vk("vkCreateVideoSessionParametersKHR")) + } + + /// Group NAL units into pictures, absorbing parameter sets. Returns the + /// pictures plus, if any slice was skipped for missing parameter sets, the + /// reason a keyframe is needed. + fn split_pictures<'a>(&mut self, data: &'a [u8]) -> Result<(Vec>, Option)> { + let mut pictures: Vec> = Vec::new(); + let mut awaiting: Option = None; + // Whether the slices of the picture in progress are being dropped, + // because its first segment could not be used. + let mut dropping = false; + + for nal in parser::iter_nal_units(data) { + // Only the base layer is decoded. + if nal.header.layer_id != 0 { + continue; + } + match nal.nal_type() { + NalType::Vps => { + let vps = parser::parse_vps(nal.payload())?; + self.vps_map.insert(vps.vps_video_parameter_set_id, vps); + } + NalType::Sps => { + let sps = sps::parse_sps(nal.payload())?; + debug!( + "H.265 SPS id={} {}x{} profile={}", + sps.sps_seq_parameter_set_id, + sps.pic_width_in_luma_samples, + sps.pic_height_in_luma_samples, + sps.profile_tier_level.general_profile_idc + ); + self.sps_map.insert(sps.sps_seq_parameter_set_id, sps); + } + NalType::Pps => { + let pps = pps::parse_pps(nal.payload())?; + self.pps_map.insert(pps.pps_pic_parameter_set_id, pps); + } + NalType::Eos => self.pending_eos = true, + t if t.is_vcl() && !matches!(t, NalType::Other(_)) => { + if slice::starts_picture(&nal) { + dropping = false; + match self.begin_picture(&nal)? { + Ok(picture) => pictures.push(picture), + Err(reason) => { + awaiting.get_or_insert(reason); + dropping = true; + } + } + } else if !dropping && let Some(picture) = pictures.last_mut() { + picture.segments.push(nal.data); + } + } + // SEI, AUD, and everything else is not needed by the driver. + _ => {} + } + } + Ok((pictures, awaiting)) + } + + /// Start a picture at its first slice segment. The inner `Err` is the + /// reason it cannot be decoded: parameter sets that have not been seen. + fn begin_picture<'a>( + &mut self, + nal: &NalUnit<'a>, + ) -> Result, String>> { + let pps_id = slice::slice_pps_id(nal)?; + let Some(pps) = self.pps_map.get(&pps_id) else { + return Ok(Err(format!("PPS {pps_id} not yet received"))); + }; + let Some(sps) = self.sps_map.get(&pps.pps_seq_parameter_set_id) else { + return Ok(Err(format!( + "SPS {} not yet received", + pps.pps_seq_parameter_set_id + ))); + }; + let Some(vps) = self.vps_map.get(&sps.sps_video_parameter_set_id) else { + return Ok(Err(format!( + "VPS {} not yet received", + sps.sps_video_parameter_set_id + ))); + }; + let header = slice::parse_slice_header(nal, sps, pps)?; + let after_eos = std::mem::take(&mut self.pending_eos); + Ok(Ok(Picture { + nal: nal.header, + header, + segments: vec![nal.data], + after_eos, + vps: vps.clone(), + sps: sps.clone(), + pps: pps.clone(), + })) + } + + /// Record and submit the decode of a single picture, without waiting for + /// it. + fn decode_picture(&mut self, picture: &Picture, pts: u64) -> Result { + let sps = &picture.sps; + let dpb = self.dpb.as_mut().expect("activated above"); + if picture.after_eos { + dpb.end_of_sequence(); + } + let state = match dpb.begin_picture(&picture.nal, &picture.header, sps)? { + PictureDecision::Decode(state) => state, + PictureDecision::Skip(SkipReason::RaslWithoutReferences) => { + return Ok(Decoded::Skipped); + } + PictureDecision::Skip(SkipReason::AwaitingIrap) => { + debug!("H.265 decode: picture before the first IRAP picture skipped"); + return Ok(Decoded::Skipped); + } + }; + if state.missing_reference { + // A picture that predicts from one the decoder does not have + // would decode to garbage, and so would everything after it that + // predicts from it in turn. + return Ok(Decoded::NeedsKeyframe(format!( + "picture POC {} references a picture that is not in the DPB", + state.poc + ))); + } + + // Every slot can be busy because a frame the caller still holds pins + // one; wait for them to drop it, as the encoder waits for a free slot. + let slot = loop { + let dpb = self.dpb.as_mut().expect("active"); + if let Some(slot) = dpb.try_allocate_slot() { + break slot; + } + if !dpb.has_pinned_slots() { + return Err(PixelForgeError::InvalidInput( + "H.265 decode: no free DPB slot (stream exceeds negotiated DPB size)" + .to_string(), + )); + } + self.common.session()?.slot_pins.wait_for_release(); + }; + + // Claim the pipeline slot before staging into its buffer; see the + // H.264 decoder for why the order matters. + self.common.begin_decode_commands()?; + let (buffer_range, offsets) = self.common.stage_slices(&picture.segments, &START_CODE)?; + self.common.record_barriers(slot); + self.record_decode(picture, &state, slot, buffer_range, &offsets)?; + self.common.submit_decode()?; + + let (width, height) = sps.display_dimensions(); + let frame = + self.common + .decoded_picture(slot, width, height, pts, state.poc, state.is_keyframe); + self.dpb.as_mut().expect("active").end_picture(slot, &state); + + Ok(if state.pic_output_flag { + Decoded::Shown(frame) + } else { + Decoded::Hidden + }) + } + + /// Fill and link this pipeline slot's record, and record the coding + /// commands from it. + fn record_decode( + &mut self, + picture: &Picture, + state: &PictureState, + slot: u8, + buffer_range: u64, + offsets: &[u32], + ) -> Result<()> { + let active = self.common.session.as_ref().expect("active"); + + // The slot's previous decode has finished (`begin_decode_commands` + // waited for it), so its record can be rewritten. + let record = &mut self.records[self.common.pipeline.current_index()]; + record.reset(offsets); + + // Every picture the DPB holds, so the driver keeps them all; the lists + // in the picture info say which the current picture predicts from. + for r in self.dpb.as_ref().expect("active").references() { + // SAFETY: plain C data, for which all-zero is valid. + let mut flags: ash::vk::native::StdVideoDecodeH265ReferenceInfoFlags = + unsafe { std::mem::zeroed() }; + flags.set_used_for_long_term_reference(r.long_term as u32); + record.push_reference( + r.slot, + active.dpb_entry(r.slot).2, + None, + ash::vk::native::StdVideoDecodeH265ReferenceInfo { + flags, + PicOrderCntVal: r.poc, + }, + ); + } + let setup_std = ash::vk::native::StdVideoDecodeH265ReferenceInfo { + // SAFETY: plain C data, for which all-zero is valid. + flags: unsafe { std::mem::zeroed() }, + PicOrderCntVal: state.poc, + }; + + *record.picture_mut() = picture_info(picture, state)?; + record.link(&LinkParams { + extent: vk::Extent2D { + width: active.coded_width, + height: active.coded_height, + }, + session: active.session, + session_params: active.session_params, + bitstream: self.common.bitstream_buffer(), + bitstream_range: buffer_range, + setup_slot: slot, + setup_view: active.dpb_entry(slot).2, + output_view: active.output_image.map(|(_, _, view)| view), + setup_std, + }); + self.common.record_coding(record); + Ok(()) + } +} + +/// The Std picture info for a picture about to be decoded. +fn picture_info( + picture: &Picture, + state: &PictureState, +) -> Result { + let header = &picture.header; + // SAFETY: plain C data, for which all-zero is valid. + let mut flags: ash::vk::native::StdVideoDecodeH265PictureInfoFlags = + unsafe { std::mem::zeroed() }; + flags.set_IrapPicFlag(state.nal_type.is_irap() as u32); + flags.set_IdrPicFlag(state.nal_type.is_idr() as u32); + flags.set_IsReference(state.is_reference as u32); + flags.set_short_term_ref_pic_set_sps_flag(header.short_term_ref_pic_set_sps_flag as u32); + Ok(ash::vk::native::StdVideoDecodeH265PictureInfo { + flags, + sps_video_parameter_set_id: picture.sps.sps_video_parameter_set_id, + pps_seq_parameter_set_id: picture.pps.pps_seq_parameter_set_id, + pps_pic_parameter_set_id: picture.pps.pps_pic_parameter_set_id, + NumDeltaPocsOfRefRpsIdx: header.num_delta_pocs_of_ref_rps_idx(&picture.sps), + PicOrderCntVal: state.poc, + NumBitsForSTRefPicSetInSlice: header.num_bits_for_st_ref_pic_set as u16, + reserved: 0, + RefPicSetStCurrBefore: RefPicSet::vulkan_list(&state.rps.st_curr_before)?, + RefPicSetStCurrAfter: RefPicSet::vulkan_list(&state.rps.st_curr_after)?, + RefPicSetLtCurr: RefPicSet::vulkan_list(&state.rps.lt_curr)?, + }) +} + +impl CodecDecoder for H265Decoder { + type Unit<'a> = Picture<'a>; + + const NAME: &'static str = "H.265"; + + fn common(&self) -> &DecoderCommon { + &self.common + } + + fn common_mut(&mut self) -> &mut DecoderCommon { + &mut self.common + } + + fn complete_prefix(buffer: &[u8]) -> usize { + super::complete_prefix(buffer) + } + + fn split_units<'a>(&mut self, data: &'a [u8]) -> Result<(Vec>, Option)> { + self.split_pictures(data) + } + + fn is_keyframe(picture: &Picture<'_>) -> bool { + picture.nal.nal_type.is_irap() + } + + fn decode_unit(&mut self, picture: &Picture<'_>, pts: u64) -> Result { + // (Re)build the session if the stream's parameter sets changed. + self.activate(&picture.vps, &picture.sps, &picture.pps)?; + self.decode_picture(picture, pts) + } + + fn reorder_depth(&self) -> usize { + self.reorder_depth + } +} diff --git a/src/decoder/h265/slice.rs b/src/decoder/h265/slice.rs new file mode 100644 index 00000000..e78b25d3 --- /dev/null +++ b/src/decoder/h265/slice.rs @@ -0,0 +1,268 @@ +//! The leading part of the H.265 slice segment header (clause 7.3.6.1). +//! +//! Parsing stops after `slice_temporal_mvp_enabled_flag`. Everything the host +//! needs is before that point: the picture order count, the short-term and +//! long-term reference picture set syntax, and the bit count Vulkan wants for +//! the inline short-term set. What follows (SAO, reference list sizes and +//! modification, weighted prediction, QP and deblocking overrides, entry +//! points) is parsed by the driver from the slice NAL unit itself. + +use super::parser::{NalType, NalUnit, Reader, ceil_log2, invalid}; +use super::pps::Pps; +use super::rps::{MAX_ST_REFS, StRefPicSet, parse_st_ref_pic_set}; +use super::sps::Sps; +use crate::error::Result; + +/// H.265 slice types (Table 7-7). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) enum SliceType { + B, + P, + #[default] + I, +} + +/// One long-term reference picture entry of the slice header, resolved +/// against the SPS where it was an index into `lt_ref_pic_poc_lsb_sps`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) struct LongTermRef { + /// `lt_idx_sps[i]` for an entry taken from the SPS, `None` for one coded + /// in the slice header. + pub lt_idx_sps: Option, + /// `PocLsbLt[i]`. + pub poc_lsb_lt: u32, + /// `UsedByCurrPicLt[i]`. + pub used_by_curr_pic_lt: bool, + pub delta_poc_msb_present_flag: bool, + /// `delta_poc_msb_cycle_lt[i]` as coded. + pub delta_poc_msb_cycle_lt: u32, + /// `DeltaPocMsbCycleLt[i]` (equation 7-52), which accumulates the coded + /// values within each of the two groups (SPS entries, then slice entries). + pub delta_poc_msb_cycle_lt_accumulated: u32, +} + +/// The slice segment header fields the host needs. +/// +/// A dependent slice segment (`dependent_slice_segment_flag`) codes only the +/// fields up to `slice_segment_address`; the rest of its header is that of the +/// preceding independent segment, and is left at defaults here. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct SliceHeader { + pub first_slice_segment_in_pic_flag: bool, + /// Coded only for IRAP pictures. + pub no_output_of_prior_pics_flag: bool, + pub slice_pic_parameter_set_id: u8, + pub dependent_slice_segment_flag: bool, + pub slice_segment_address: u32, + pub slice_type: SliceType, + /// 1 when not coded (`output_flag_present_flag` = 0). + pub pic_output_flag: bool, + pub colour_plane_id: u8, + /// 0 for IDR pictures, which do not code it. + pub slice_pic_order_cnt_lsb: u32, + pub short_term_ref_pic_set_sps_flag: bool, + pub short_term_ref_pic_set_idx: u8, + /// The set coded in the slice header, when + /// `short_term_ref_pic_set_sps_flag` is 0 (and the picture is not IDR). + pub st_ref_pic_set: Option, + /// RBSP bits the inline `st_ref_pic_set()` occupied: Vulkan's + /// `NumBitsForSTRefPicSetInSlice`. 0 when the set came from the SPS. + pub num_bits_for_st_ref_pic_set: u32, + pub num_long_term_sps: u8, + pub num_long_term_pics: u8, + /// `num_long_term_sps + num_long_term_pics` entries, SPS ones first. + pub long_term_refs: Vec, + pub slice_temporal_mvp_enabled_flag: bool, +} + +impl SliceHeader { + /// The short-term set in effect: the inline one, or the SPS set + /// `short_term_ref_pic_set_idx` selects. `None` for an IDR picture, whose + /// reference picture set is empty. + pub fn short_term_ref_pic_set<'a>(&'a self, sps: &'a Sps) -> Option<&'a StRefPicSet> { + if self.short_term_ref_pic_set_sps_flag { + sps.short_term_ref_pic_sets + .get(self.short_term_ref_pic_set_idx as usize) + } else { + self.st_ref_pic_set.as_ref() + } + } + + /// Vulkan's `NumDeltaPocsOfRefRpsIdx`: `NumDeltaPocs[RefRpsIdx]` when the + /// slice header codes its own set with inter RPS prediction, else 0. + pub fn num_delta_pocs_of_ref_rps_idx(&self, sps: &Sps) -> u8 { + let Some(set) = self.st_ref_pic_set.as_ref() else { + return 0; + }; + set.ref_rps_idx + .and_then(|idx| sps.short_term_ref_pic_sets.get(idx)) + .map_or(0, |reference| reference.rps.num_delta_pocs() as u8) + } +} + +/// The PPS a slice segment refers to, read without parsing the rest of the +/// header, so the caller can find the PPS (and through it the SPS) first. +pub(crate) fn slice_pps_id(nal: &NalUnit) -> Result { + let mut r = Reader::new(nal.payload()); + let _first_slice_segment_in_pic_flag = r.flag()?; + if nal.nal_type().is_irap() { + let _no_output_of_prior_pics_flag = r.flag()?; + } + Ok(r.ue_max(63, "slice_pic_parameter_set_id")? as u8) +} + +/// Parse the leading part of a slice segment header; see the module +/// documentation for where it stops. +pub(crate) fn parse_slice_header(nal: &NalUnit, sps: &Sps, pps: &Pps) -> Result { + let nal_type = nal.nal_type(); + if !nal_type.is_vcl() { + return Err(invalid("slice header parse of a non-VCL NAL unit")); + } + let mut r = Reader::new(nal.payload()); + let mut h = SliceHeader { + first_slice_segment_in_pic_flag: r.flag()?, + pic_output_flag: true, + ..Default::default() + }; + if nal_type.is_irap() { + h.no_output_of_prior_pics_flag = r.flag()?; + } + h.slice_pic_parameter_set_id = r.ue_max(63, "slice_pic_parameter_set_id")? as u8; + if h.slice_pic_parameter_set_id != pps.pps_pic_parameter_set_id + || pps.pps_seq_parameter_set_id != sps.sps_seq_parameter_set_id + { + return Err(invalid("slice parsed against the wrong PPS or SPS")); + } + + if !h.first_slice_segment_in_pic_flag { + if pps.dependent_slice_segments_enabled_flag { + h.dependent_slice_segment_flag = r.flag()?; + } + let pic_size = sps.pic_size_in_ctbs(); + h.slice_segment_address = r.bits(ceil_log2(pic_size))?; + if h.slice_segment_address >= pic_size { + return Err(invalid("slice_segment_address outside the picture")); + } + } + if h.dependent_slice_segment_flag { + return Ok(h); + } + + // slice_reserved_flag[i]. + r.skip(pps.num_extra_slice_header_bits as u32)?; + h.slice_type = match r.ue()? { + 0 => SliceType::B, + 1 => SliceType::P, + 2 => SliceType::I, + other => return Err(invalid(format_args!("invalid slice_type {other}"))), + }; + if pps.output_flag_present_flag { + h.pic_output_flag = r.flag()?; + } + if sps.separate_colour_plane_flag { + h.colour_plane_id = r.bits(2)? as u8; + } + if nal_type.is_idr() { + return Ok(h); + } + + let lsb_bits = sps.log2_max_pic_order_cnt_lsb_minus4 as u32 + 4; + h.slice_pic_order_cnt_lsb = r.bits(lsb_bits)?; + h.short_term_ref_pic_set_sps_flag = r.flag()?; + let num_sets = sps.num_short_term_ref_pic_sets(); + if !h.short_term_ref_pic_set_sps_flag { + let start = r.position(); + let set = parse_st_ref_pic_set( + &mut r, + num_sets, + num_sets, + &sps.short_term_ref_pic_sets, + sps.max_dec_pic_buffering() - 1, + )?; + h.num_bits_for_st_ref_pic_set = (r.position() - start) as u32; + h.st_ref_pic_set = Some(set); + } else { + if num_sets == 0 { + return Err(invalid( + "slice selects an SPS short-term set, but the SPS has none", + )); + } + if num_sets > 1 { + h.short_term_ref_pic_set_idx = r.bits(ceil_log2(num_sets as u32))? as u8; + } + if h.short_term_ref_pic_set_idx as usize >= num_sets { + return Err(invalid("short_term_ref_pic_set_idx out of range")); + } + } + + if sps.long_term_ref_pics_present_flag { + parse_long_term_refs(&mut r, &mut h, sps)?; + } + if sps.sps_temporal_mvp_enabled_flag { + h.slice_temporal_mvp_enabled_flag = r.flag()?; + } + Ok(h) +} + +/// The long-term part of the slice header, from `num_long_term_sps` to the +/// last `delta_poc_msb_cycle_lt`. +fn parse_long_term_refs(r: &mut Reader, h: &mut SliceHeader, sps: &Sps) -> Result<()> { + let num_sps_entries = sps.num_long_term_ref_pics_sps(); + if num_sps_entries > 0 { + h.num_long_term_sps = r.ue_max(num_sps_entries as u32, "num_long_term_sps")? as u8; + } + h.num_long_term_pics = r.ue_max( + MAX_ST_REFS as u32 - h.num_long_term_sps as u32, + "num_long_term_pics", + )? as u8; + + let lsb_bits = sps.log2_max_pic_order_cnt_lsb_minus4 as u32 + 4; + let idx_bits = ceil_log2(num_sps_entries as u32); + let total = h.num_long_term_sps as usize + h.num_long_term_pics as usize; + let mut accumulated = 0u32; + for i in 0..total { + let mut entry = LongTermRef::default(); + if i < h.num_long_term_sps as usize { + let idx = if num_sps_entries > 1 { + r.bits(idx_bits)? + } else { + 0 + }; + let lsb = sps + .long_term_ref_pics + .lt_ref_pic_poc_lsb_sps + .get(idx as usize) + .ok_or_else(|| invalid("lt_idx_sps out of range"))?; + entry.lt_idx_sps = Some(idx as u8); + entry.poc_lsb_lt = *lsb; + entry.used_by_curr_pic_lt = + sps.long_term_ref_pics.used_by_curr_pic_lt_sps_flag & (1 << idx) != 0; + } else { + entry.poc_lsb_lt = r.bits(lsb_bits)?; + entry.used_by_curr_pic_lt = r.flag()?; + } + entry.delta_poc_msb_present_flag = r.flag()?; + if entry.delta_poc_msb_present_flag { + entry.delta_poc_msb_cycle_lt = r.ue()?; + } + // Equation 7-52: the sum restarts at the first slice-coded entry. + if i == 0 || i == h.num_long_term_sps as usize { + accumulated = entry.delta_poc_msb_cycle_lt; + } else { + accumulated = accumulated.saturating_add(entry.delta_poc_msb_cycle_lt); + } + entry.delta_poc_msb_cycle_lt_accumulated = accumulated; + h.long_term_refs.push(entry); + } + Ok(()) +} + +/// Whether `nal` starts a new coded picture: a slice segment of a known type +/// with `first_slice_segment_in_pic_flag` set, which is the first bit after +/// the NAL header. Reserved VCL types are ignored, as clause 7.4.2.2 requires. +pub(crate) fn starts_picture(nal: &NalUnit) -> bool { + let nal_type = nal.nal_type(); + nal_type.is_vcl() + && !matches!(nal_type, NalType::Other(_)) + && nal.payload().first().is_some_and(|b| b & 0x80 != 0) +} diff --git a/src/decoder/h265/sps.rs b/src/decoder/h265/sps.rs new file mode 100644 index 00000000..5a7a0940 --- /dev/null +++ b/src/decoder/h265/sps.rs @@ -0,0 +1,466 @@ +//! The H.265 sequence parameter set (clause 7.3.2.2) and its VUI (Annex E). +//! +//! Field names follow the standard, which are also the names +//! `StdVideoH265SequenceParameterSet` and its sub-structures use, so filling +//! the Vulkan structures is a field-by-field copy. + +use super::parser::{ + DecPicBufMgr, MAX_SUB_LAYERS, ProfileTierLevel, Reader, invalid, parse_dec_pic_buf_mgr, + parse_profile_tier_level, skip_hrd_parameters, +}; +use super::rps::{StRefPicSet, parse_st_ref_pic_set}; +use super::scaling::{ScalingLists, parse_scaling_list_data}; +use crate::error::Result; + +/// Most short-term reference picture sets an SPS may code. +pub(crate) const MAX_SPS_ST_REF_PIC_SETS: usize = 64; +/// Most long-term reference pictures an SPS may list. +pub(crate) const MAX_SPS_LT_REF_PICS: usize = 32; + +/// `vui_parameters()` (clause E.2.1), laid out like +/// `StdVideoH265SequenceParameterSetVui`. +/// +/// HRD parameters are parsed past but not kept; `vui_hrd_parameters_present_flag` +/// records whether there were any. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct Vui { + pub aspect_ratio_info_present_flag: bool, + pub aspect_ratio_idc: u8, + pub sar_width: u16, + pub sar_height: u16, + pub overscan_info_present_flag: bool, + pub overscan_appropriate_flag: bool, + pub video_signal_type_present_flag: bool, + pub video_format: u8, + pub video_full_range_flag: bool, + pub colour_description_present_flag: bool, + pub colour_primaries: u8, + pub transfer_characteristics: u8, + pub matrix_coeffs: u8, + pub chroma_loc_info_present_flag: bool, + pub chroma_sample_loc_type_top_field: u8, + pub chroma_sample_loc_type_bottom_field: u8, + pub neutral_chroma_indication_flag: bool, + pub field_seq_flag: bool, + pub frame_field_info_present_flag: bool, + pub default_display_window_flag: bool, + pub def_disp_win_left_offset: u16, + pub def_disp_win_right_offset: u16, + pub def_disp_win_top_offset: u16, + pub def_disp_win_bottom_offset: u16, + pub vui_timing_info_present_flag: bool, + pub vui_num_units_in_tick: u32, + pub vui_time_scale: u32, + pub vui_poc_proportional_to_timing_flag: bool, + pub vui_num_ticks_poc_diff_one_minus1: u32, + pub vui_hrd_parameters_present_flag: bool, + pub bitstream_restriction_flag: bool, + pub tiles_fixed_structure_flag: bool, + pub motion_vectors_over_pic_boundaries_flag: bool, + pub restricted_ref_pic_lists_flag: bool, + pub min_spatial_segmentation_idc: u16, + pub max_bytes_per_pic_denom: u8, + pub max_bits_per_min_cu_denom: u8, + pub log2_max_mv_length_horizontal: u8, + pub log2_max_mv_length_vertical: u8, +} + +/// Parse `vui_parameters()`. +fn parse_vui(r: &mut Reader, sps_max_sub_layers_minus1: u8) -> Result { + let mut vui = Vui { + aspect_ratio_info_present_flag: r.flag()?, + ..Default::default() + }; + if vui.aspect_ratio_info_present_flag { + vui.aspect_ratio_idc = r.bits(8)? as u8; + // EXTENDED_SAR. + if vui.aspect_ratio_idc == 255 { + vui.sar_width = r.bits(16)? as u16; + vui.sar_height = r.bits(16)? as u16; + } + } + vui.overscan_info_present_flag = r.flag()?; + if vui.overscan_info_present_flag { + vui.overscan_appropriate_flag = r.flag()?; + } + vui.video_signal_type_present_flag = r.flag()?; + if vui.video_signal_type_present_flag { + vui.video_format = r.bits(3)? as u8; + vui.video_full_range_flag = r.flag()?; + vui.colour_description_present_flag = r.flag()?; + if vui.colour_description_present_flag { + vui.colour_primaries = r.bits(8)? as u8; + vui.transfer_characteristics = r.bits(8)? as u8; + vui.matrix_coeffs = r.bits(8)? as u8; + } + } + vui.chroma_loc_info_present_flag = r.flag()?; + if vui.chroma_loc_info_present_flag { + vui.chroma_sample_loc_type_top_field = r.ue_max(5, "chroma_sample_loc_type")? as u8; + vui.chroma_sample_loc_type_bottom_field = r.ue_max(5, "chroma_sample_loc_type")? as u8; + } + vui.neutral_chroma_indication_flag = r.flag()?; + vui.field_seq_flag = r.flag()?; + vui.frame_field_info_present_flag = r.flag()?; + vui.default_display_window_flag = r.flag()?; + if vui.default_display_window_flag { + vui.def_disp_win_left_offset = r.ue_max(u16::MAX as u32, "def_disp_win offset")? as u16; + vui.def_disp_win_right_offset = r.ue_max(u16::MAX as u32, "def_disp_win offset")? as u16; + vui.def_disp_win_top_offset = r.ue_max(u16::MAX as u32, "def_disp_win offset")? as u16; + vui.def_disp_win_bottom_offset = r.ue_max(u16::MAX as u32, "def_disp_win offset")? as u16; + } + vui.vui_timing_info_present_flag = r.flag()?; + if vui.vui_timing_info_present_flag { + vui.vui_num_units_in_tick = r.bits(32)?; + vui.vui_time_scale = r.bits(32)?; + vui.vui_poc_proportional_to_timing_flag = r.flag()?; + if vui.vui_poc_proportional_to_timing_flag { + vui.vui_num_ticks_poc_diff_one_minus1 = r.ue()?; + } + vui.vui_hrd_parameters_present_flag = r.flag()?; + if vui.vui_hrd_parameters_present_flag { + skip_hrd_parameters(r, true, sps_max_sub_layers_minus1)?; + } + } + vui.bitstream_restriction_flag = r.flag()?; + if vui.bitstream_restriction_flag { + vui.tiles_fixed_structure_flag = r.flag()?; + vui.motion_vectors_over_pic_boundaries_flag = r.flag()?; + vui.restricted_ref_pic_lists_flag = r.flag()?; + vui.min_spatial_segmentation_idc = r.ue_max(4095, "min_spatial_segmentation_idc")? as u16; + vui.max_bytes_per_pic_denom = r.ue_max(16, "max_bytes_per_pic_denom")? as u8; + vui.max_bits_per_min_cu_denom = r.ue_max(16, "max_bits_per_min_cu_denom")? as u8; + vui.log2_max_mv_length_horizontal = r.ue_max(16, "log2_max_mv_length_horizontal")? as u8; + vui.log2_max_mv_length_vertical = r.ue_max(15, "log2_max_mv_length_vertical")? as u8; + } + Ok(vui) +} + +/// `sps_range_extension()` (clause 7.3.2.2.2). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct SpsRangeExtension { + pub transform_skip_rotation_enabled_flag: bool, + pub transform_skip_context_enabled_flag: bool, + pub implicit_rdpcm_enabled_flag: bool, + pub explicit_rdpcm_enabled_flag: bool, + pub extended_precision_processing_flag: bool, + pub intra_smoothing_disabled_flag: bool, + pub high_precision_offsets_enabled_flag: bool, + pub persistent_rice_adaptation_enabled_flag: bool, + pub cabac_bypass_alignment_enabled_flag: bool, +} + +/// Long-term reference pictures listed in the SPS, as +/// `StdVideoH265LongTermRefPicsSps` carries them. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct LongTermRefPicsSps { + /// `lt_ref_pic_poc_lsb_sps[i]`, `num_long_term_ref_pics_sps` entries. + pub lt_ref_pic_poc_lsb_sps: Vec, + /// `used_by_curr_pic_lt_sps_flag[i]`, bit `i`. + pub used_by_curr_pic_lt_sps_flag: u32, +} + +/// Parsed H.265 sequence parameter set. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct Sps { + pub sps_video_parameter_set_id: u8, + pub sps_max_sub_layers_minus1: u8, + pub sps_temporal_id_nesting_flag: bool, + pub profile_tier_level: ProfileTierLevel, + pub sps_seq_parameter_set_id: u8, + pub chroma_format_idc: u8, + pub separate_colour_plane_flag: bool, + pub pic_width_in_luma_samples: u32, + pub pic_height_in_luma_samples: u32, + pub conformance_window_flag: bool, + pub conf_win_left_offset: u32, + pub conf_win_right_offset: u32, + pub conf_win_top_offset: u32, + pub conf_win_bottom_offset: u32, + pub bit_depth_luma_minus8: u8, + pub bit_depth_chroma_minus8: u8, + pub log2_max_pic_order_cnt_lsb_minus4: u8, + pub sps_sub_layer_ordering_info_present_flag: bool, + /// Filled for every sub-layer up to `sps_max_sub_layers_minus1`. + pub dec_pic_buf_mgr: DecPicBufMgr, + pub log2_min_luma_coding_block_size_minus3: u8, + pub log2_diff_max_min_luma_coding_block_size: u8, + pub log2_min_luma_transform_block_size_minus2: u8, + pub log2_diff_max_min_luma_transform_block_size: u8, + pub max_transform_hierarchy_depth_inter: u8, + pub max_transform_hierarchy_depth_intra: u8, + pub scaling_list_enabled_flag: bool, + pub sps_scaling_list_data_present_flag: bool, + /// The lists in effect for the sequence: the coded ones when + /// `sps_scaling_list_data_present_flag` is set, the defaults when only + /// `scaling_list_enabled_flag` is, `None` when scaling lists are off (flat + /// scaling). A PPS may override them. + pub scaling_lists: Option, + pub amp_enabled_flag: bool, + pub sample_adaptive_offset_enabled_flag: bool, + pub pcm_enabled_flag: bool, + pub pcm_sample_bit_depth_luma_minus1: u8, + pub pcm_sample_bit_depth_chroma_minus1: u8, + pub log2_min_pcm_luma_coding_block_size_minus3: u8, + pub log2_diff_max_min_pcm_luma_coding_block_size: u8, + pub pcm_loop_filter_disabled_flag: bool, + /// The `num_short_term_ref_pic_sets` sets coded in the SPS. + pub short_term_ref_pic_sets: Vec, + pub long_term_ref_pics_present_flag: bool, + pub long_term_ref_pics: LongTermRefPicsSps, + pub sps_temporal_mvp_enabled_flag: bool, + pub strong_intra_smoothing_enabled_flag: bool, + pub vui_parameters_present_flag: bool, + pub vui: Option, + pub sps_extension_present_flag: bool, + pub sps_range_extension_flag: bool, + pub sps_multilayer_extension_flag: bool, + pub sps_3d_extension_flag: bool, + pub sps_scc_extension_flag: bool, + pub sps_extension_4bits: u8, + pub range_extension: SpsRangeExtension, +} + +impl Sps { + /// `num_short_term_ref_pic_sets`. + pub fn num_short_term_ref_pic_sets(&self) -> usize { + self.short_term_ref_pic_sets.len() + } + + /// `num_long_term_ref_pics_sps`. + pub fn num_long_term_ref_pics_sps(&self) -> usize { + self.long_term_ref_pics.lt_ref_pic_poc_lsb_sps.len() + } + + /// `MaxPicOrderCntLsb`. + pub fn max_pic_order_cnt_lsb(&self) -> u32 { + 1 << (self.log2_max_pic_order_cnt_lsb_minus4 as u32 + 4) + } + + /// `CtbLog2SizeY`. + pub fn ctb_log2_size(&self) -> u32 { + self.log2_min_luma_coding_block_size_minus3 as u32 + + 3 + + self.log2_diff_max_min_luma_coding_block_size as u32 + } + + /// `PicWidthInCtbsY`. + pub fn pic_width_in_ctbs(&self) -> u32 { + self.pic_width_in_luma_samples + .div_ceil(1 << self.ctb_log2_size()) + } + + /// `PicHeightInCtbsY`. + pub fn pic_height_in_ctbs(&self) -> u32 { + self.pic_height_in_luma_samples + .div_ceil(1 << self.ctb_log2_size()) + } + + /// `PicSizeInCtbsY`. + pub fn pic_size_in_ctbs(&self) -> u32 { + self.pic_width_in_ctbs() * self.pic_height_in_ctbs() + } + + /// `ChromaArrayType`: 0 for monochrome or separately coded colour planes. + pub fn chroma_array_type(&self) -> u8 { + if self.separate_colour_plane_flag { + 0 + } else { + self.chroma_format_idc + } + } + + /// `HighestTid` when every sub-layer is decoded. + pub fn highest_tid(&self) -> usize { + self.sps_max_sub_layers_minus1 as usize + } + + /// Pictures the DPB must hold (`sps_max_dec_pic_buffering_minus1 + 1` of + /// the highest sub-layer), the current picture included. + pub fn max_dec_pic_buffering(&self) -> usize { + self.dec_pic_buf_mgr.max_dec_pic_buffering_minus1[self.highest_tid()] as usize + 1 + } + + /// `sps_max_num_reorder_pics` of the highest sub-layer: how many pictures + /// can precede any picture in decoding order and follow it in output + /// order, which is the depth a reorder buffer needs. + pub fn reorder_depth(&self) -> usize { + self.dec_pic_buf_mgr.max_num_reorder_pics[self.highest_tid()] as usize + } + + /// `SpsMaxLatencyPictures` of the highest sub-layer, or `None` when + /// `sps_max_latency_increase_plus1` is 0 (no limit). The output process + /// uses only the reorder depth, so this is for tests. + #[cfg(test)] + pub fn max_latency_pictures(&self) -> Option { + let tid = self.highest_tid(); + let plus1 = self.dec_pic_buf_mgr.max_latency_increase_plus1[tid] as usize; + (plus1 != 0).then(|| self.dec_pic_buf_mgr.max_num_reorder_pics[tid] as usize + plus1 - 1) + } + + /// Display (conformance-window cropped) dimensions. + /// + /// The offsets are in chroma sample units (clause 7.4.3.2.1). + pub fn display_dimensions(&self) -> (u32, u32) { + let (sub_width_c, sub_height_c) = match self.chroma_array_type() { + 1 => (2, 2), + 2 => (2, 1), + _ => (1, 1), + }; + let mut width = self.pic_width_in_luma_samples; + let mut height = self.pic_height_in_luma_samples; + if self.conformance_window_flag { + width = width.saturating_sub( + sub_width_c * (self.conf_win_left_offset + self.conf_win_right_offset), + ); + height = height.saturating_sub( + sub_height_c * (self.conf_win_top_offset + self.conf_win_bottom_offset), + ); + } + (width, height) + } +} + +/// Parse an SPS NAL payload (EBSP, after the 2-byte NAL header). +pub(crate) fn parse_sps(payload: &[u8]) -> Result { + let mut r = Reader::new(payload); + let mut sps = Sps { + sps_video_parameter_set_id: r.bits(4)? as u8, + sps_max_sub_layers_minus1: r.bits(3)? as u8, + sps_temporal_id_nesting_flag: r.flag()?, + ..Default::default() + }; + if sps.sps_max_sub_layers_minus1 as usize >= MAX_SUB_LAYERS { + return Err(invalid("SPS: sps_max_sub_layers_minus1 is 7")); + } + sps.profile_tier_level = parse_profile_tier_level(&mut r, sps.sps_max_sub_layers_minus1)?; + sps.sps_seq_parameter_set_id = r.ue_max(15, "sps_seq_parameter_set_id")? as u8; + sps.chroma_format_idc = r.ue_max(3, "chroma_format_idc")? as u8; + if sps.chroma_format_idc == 3 { + sps.separate_colour_plane_flag = r.flag()?; + } + sps.pic_width_in_luma_samples = r.ue_max(16888, "pic_width_in_luma_samples")?; + sps.pic_height_in_luma_samples = r.ue_max(16888, "pic_height_in_luma_samples")?; + if sps.pic_width_in_luma_samples == 0 || sps.pic_height_in_luma_samples == 0 { + return Err(invalid("SPS: zero picture size")); + } + sps.conformance_window_flag = r.flag()?; + if sps.conformance_window_flag { + sps.conf_win_left_offset = r.ue()?; + sps.conf_win_right_offset = r.ue()?; + sps.conf_win_top_offset = r.ue()?; + sps.conf_win_bottom_offset = r.ue()?; + } + sps.bit_depth_luma_minus8 = r.ue_max(8, "bit_depth_luma_minus8")? as u8; + sps.bit_depth_chroma_minus8 = r.ue_max(8, "bit_depth_chroma_minus8")? as u8; + sps.log2_max_pic_order_cnt_lsb_minus4 = + r.ue_max(12, "log2_max_pic_order_cnt_lsb_minus4")? as u8; + sps.sps_sub_layer_ordering_info_present_flag = r.flag()?; + sps.dec_pic_buf_mgr = parse_dec_pic_buf_mgr( + &mut r, + sps.sps_max_sub_layers_minus1, + sps.sps_sub_layer_ordering_info_present_flag, + )?; + + sps.log2_min_luma_coding_block_size_minus3 = + r.ue_max(3, "log2_min_luma_coding_block_size_minus3")? as u8; + sps.log2_diff_max_min_luma_coding_block_size = + r.ue_max(3, "log2_diff_max_min_luma_coding_block_size")? as u8; + if sps.ctb_log2_size() > 6 { + return Err(invalid("SPS: CTB larger than 64x64")); + } + let min_cb = 1u32 << (sps.log2_min_luma_coding_block_size_minus3 + 3); + if !sps.pic_width_in_luma_samples.is_multiple_of(min_cb) + || !sps.pic_height_in_luma_samples.is_multiple_of(min_cb) + { + return Err(invalid( + "SPS: picture size is not a multiple of the minimum coding block", + )); + } + sps.log2_min_luma_transform_block_size_minus2 = + r.ue_max(3, "log2_min_luma_transform_block_size_minus2")? as u8; + sps.log2_diff_max_min_luma_transform_block_size = + r.ue_max(3, "log2_diff_max_min_luma_transform_block_size")? as u8; + sps.max_transform_hierarchy_depth_inter = + r.ue_max(4, "max_transform_hierarchy_depth_inter")? as u8; + sps.max_transform_hierarchy_depth_intra = + r.ue_max(4, "max_transform_hierarchy_depth_intra")? as u8; + + sps.scaling_list_enabled_flag = r.flag()?; + if sps.scaling_list_enabled_flag { + sps.sps_scaling_list_data_present_flag = r.flag()?; + sps.scaling_lists = Some(if sps.sps_scaling_list_data_present_flag { + parse_scaling_list_data(&mut r)? + } else { + ScalingLists::default() + }); + } + + sps.amp_enabled_flag = r.flag()?; + sps.sample_adaptive_offset_enabled_flag = r.flag()?; + sps.pcm_enabled_flag = r.flag()?; + if sps.pcm_enabled_flag { + sps.pcm_sample_bit_depth_luma_minus1 = r.bits(4)? as u8; + sps.pcm_sample_bit_depth_chroma_minus1 = r.bits(4)? as u8; + sps.log2_min_pcm_luma_coding_block_size_minus3 = + r.ue_max(2, "log2_min_pcm_luma_coding_block_size_minus3")? as u8; + sps.log2_diff_max_min_pcm_luma_coding_block_size = + r.ue_max(2, "log2_diff_max_min_pcm_luma_coding_block_size")? as u8; + sps.pcm_loop_filter_disabled_flag = r.flag()?; + } + + let num_sets = r.ue_max( + MAX_SPS_ST_REF_PIC_SETS as u32, + "num_short_term_ref_pic_sets", + )? as usize; + let max_refs = sps.max_dec_pic_buffering() - 1; + for i in 0..num_sets { + let set = + parse_st_ref_pic_set(&mut r, i, num_sets, &sps.short_term_ref_pic_sets, max_refs)?; + sps.short_term_ref_pic_sets.push(set); + } + + sps.long_term_ref_pics_present_flag = r.flag()?; + if sps.long_term_ref_pics_present_flag { + let count = r.ue_max(MAX_SPS_LT_REF_PICS as u32, "num_long_term_ref_pics_sps")?; + let lsb_bits = sps.log2_max_pic_order_cnt_lsb_minus4 as u32 + 4; + for i in 0..count { + sps.long_term_ref_pics + .lt_ref_pic_poc_lsb_sps + .push(r.bits(lsb_bits)?); + sps.long_term_ref_pics.used_by_curr_pic_lt_sps_flag |= (r.flag()? as u32) << i; + } + } + sps.sps_temporal_mvp_enabled_flag = r.flag()?; + sps.strong_intra_smoothing_enabled_flag = r.flag()?; + sps.vui_parameters_present_flag = r.flag()?; + if sps.vui_parameters_present_flag { + sps.vui = Some(parse_vui(&mut r, sps.sps_max_sub_layers_minus1)?); + } + + sps.sps_extension_present_flag = r.flag()?; + if sps.sps_extension_present_flag { + sps.sps_range_extension_flag = r.flag()?; + sps.sps_multilayer_extension_flag = r.flag()?; + sps.sps_3d_extension_flag = r.flag()?; + sps.sps_scc_extension_flag = r.flag()?; + sps.sps_extension_4bits = r.bits(4)? as u8; + } + if sps.sps_range_extension_flag { + sps.range_extension = SpsRangeExtension { + transform_skip_rotation_enabled_flag: r.flag()?, + transform_skip_context_enabled_flag: r.flag()?, + implicit_rdpcm_enabled_flag: r.flag()?, + explicit_rdpcm_enabled_flag: r.flag()?, + extended_precision_processing_flag: r.flag()?, + intra_smoothing_disabled_flag: r.flag()?, + high_precision_offsets_enabled_flag: r.flag()?, + persistent_rice_adaptation_enabled_flag: r.flag()?, + cabac_bypass_alignment_enabled_flag: r.flag()?, + }; + } + // The multilayer, 3D and SCC extensions follow. Nothing after them matters + // to a base-layer decoder, and their flags are recorded above, so parsing + // stops here. + Ok(sps) +} diff --git a/src/decoder/h265/std_params.rs b/src/decoder/h265/std_params.rs new file mode 100644 index 00000000..b7298625 --- /dev/null +++ b/src/decoder/h265/std_params.rs @@ -0,0 +1,407 @@ +//! The `StdVideoH265*` parameter sets a decode session is created with, built +//! from the parsed VPS, SPS and PPS. + +use ash::vk::native::{ + StdVideoH265DecPicBufMgr, StdVideoH265LongTermRefPicsSps, StdVideoH265PictureParameterSet, + StdVideoH265ProfileTierLevel, StdVideoH265ScalingLists, StdVideoH265SequenceParameterSet, + StdVideoH265SequenceParameterSetVui, StdVideoH265ShortTermRefPicSet, + StdVideoH265VideoParameterSet, +}; + +use super::parser::{DecPicBufMgr, ProfileTierLevel, Vps}; +use super::pps::Pps; +use super::rps::StRefPicSetSyntax; +use super::scaling::ScalingLists; +use super::sps::{Sps, Vui}; + +/// The three parameter sets, and everything they point at. +/// +/// Each sub-structure is boxed, so the pointers to it stay valid when this +/// value moves. Keep it alive until the session parameters are created. +pub(super) struct StdParameterSets { + pub vps: StdVideoH265VideoParameterSet, + pub sps: StdVideoH265SequenceParameterSet, + pub pps: StdVideoH265PictureParameterSet, + _vps_ptl: Box, + _vps_dpbm: Box, + _sps_ptl: Box, + _sps_dpbm: Box, + _sps_scaling: Option>, + _st_rps: Vec, + _lt_rps: Box, + _vui: Option>, + _pps_scaling: Option>, +} + +// SAFETY: the raw pointers only point into the boxes this value owns. +unsafe impl Send for StdParameterSets {} + +fn ptr(value: &Option>) -> *const T { + value.as_deref().map_or(std::ptr::null(), |v| v as *const T) +} + +impl StdParameterSets { + pub(super) fn new(vps: &Vps, sps: &Sps, pps: &Pps) -> Self { + let vps_ptl = Box::new(profile_tier_level(&vps.profile_tier_level)); + let vps_dpbm = Box::new(dec_pic_buf_mgr(&vps.dec_pic_buf_mgr)); + let sps_ptl = Box::new(profile_tier_level(&sps.profile_tier_level)); + let sps_dpbm = Box::new(dec_pic_buf_mgr(&sps.dec_pic_buf_mgr)); + // With scaling lists enabled but none coded, the default lists apply, + // and the driver is handed them explicitly. + let sps_scaling = sps.scaling_list_enabled_flag.then(|| { + Box::new(scaling_lists( + sps.scaling_lists + .as_ref() + .unwrap_or(&ScalingLists::default()), + )) + }); + let st_rps: Vec<_> = sps + .short_term_ref_pic_sets + .iter() + .map(|set| short_term_ref_pic_set(&set.syntax)) + .collect(); + let lt_rps = Box::new(long_term_ref_pics(sps)); + let vui = sps.vui.as_ref().map(|v| Box::new(vui(v))); + let pps_scaling = pps + .scaling_lists + .as_ref() + .filter(|_| pps.pps_scaling_list_data_present_flag) + .map(|l| Box::new(scaling_lists(l))); + + let std_vps = video_parameter_set(vps, &vps_ptl, &vps_dpbm); + let mut std_sps = sequence_parameter_set(sps); + std_sps.pProfileTierLevel = &*sps_ptl; + std_sps.pDecPicBufMgr = &*sps_dpbm; + std_sps.pScalingLists = ptr(&sps_scaling); + std_sps.pShortTermRefPicSet = if st_rps.is_empty() { + std::ptr::null() + } else { + st_rps.as_ptr() + }; + std_sps.pLongTermRefPicsSps = &*lt_rps; + std_sps.pSequenceParameterSetVui = ptr(&vui); + let mut std_pps = picture_parameter_set(pps, sps.sps_video_parameter_set_id); + std_pps.pScalingLists = ptr(&pps_scaling); + + Self { + vps: std_vps, + sps: std_sps, + pps: std_pps, + _vps_ptl: vps_ptl, + _vps_dpbm: vps_dpbm, + _sps_ptl: sps_ptl, + _sps_dpbm: sps_dpbm, + _sps_scaling: sps_scaling, + _st_rps: st_rps, + _lt_rps: lt_rps, + _vui: vui, + _pps_scaling: pps_scaling, + } + } +} + +fn profile_tier_level(ptl: &ProfileTierLevel) -> StdVideoH265ProfileTierLevel { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265ProfileTierLevel = unsafe { std::mem::zeroed() }; + out.flags + .set_general_tier_flag(ptl.general_tier_flag as u32); + out.flags + .set_general_progressive_source_flag(ptl.general_progressive_source_flag as u32); + out.flags + .set_general_interlaced_source_flag(ptl.general_interlaced_source_flag as u32); + out.flags + .set_general_non_packed_constraint_flag(ptl.general_non_packed_constraint_flag as u32); + out.flags + .set_general_frame_only_constraint_flag(ptl.general_frame_only_constraint_flag as u32); + // The Std profile enum uses the coded values. + out.general_profile_idc = ptl.general_profile_idc as u32; + out.general_level_idc = level_idc(ptl.general_level_idc); + out +} + +/// `StdVideoH265LevelIdc` for a coded `general_level_idc`, which is thirty +/// times the level. +fn level_idc(general_level_idc: u8) -> ash::vk::native::StdVideoH265LevelIdc { + use ash::vk::native::*; + match general_level_idc { + 0..=30 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_1_0, + 31..=60 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_2_0, + 61..=63 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_2_1, + 64..=90 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_3_0, + 91..=93 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_3_1, + 94..=120 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_4_0, + 121..=123 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_4_1, + 124..=150 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_5_0, + 151..=153 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_5_1, + 154..=156 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_5_2, + 157..=180 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_6_0, + 181..=183 => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_6_1, + _ => StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_6_2, + } +} + +fn dec_pic_buf_mgr(m: &DecPicBufMgr) -> StdVideoH265DecPicBufMgr { + StdVideoH265DecPicBufMgr { + max_latency_increase_plus1: m.max_latency_increase_plus1, + max_dec_pic_buffering_minus1: m.max_dec_pic_buffering_minus1, + max_num_reorder_pics: m.max_num_reorder_pics, + } +} + +fn scaling_lists(l: &ScalingLists) -> StdVideoH265ScalingLists { + StdVideoH265ScalingLists { + ScalingList4x4: l.scaling_list_4x4, + ScalingList8x8: l.scaling_list_8x8, + ScalingList16x16: l.scaling_list_16x16, + ScalingList32x32: l.scaling_list_32x32, + ScalingListDCCoef16x16: l.scaling_list_dc_coef_16x16, + ScalingListDCCoef32x32: l.scaling_list_dc_coef_32x32, + } +} + +fn short_term_ref_pic_set(s: &StRefPicSetSyntax) -> StdVideoH265ShortTermRefPicSet { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265ShortTermRefPicSet = unsafe { std::mem::zeroed() }; + out.flags + .set_inter_ref_pic_set_prediction_flag(s.inter_ref_pic_set_prediction_flag as u32); + out.flags.set_delta_rps_sign(s.delta_rps_sign as u32); + out.delta_idx_minus1 = s.delta_idx_minus1; + // Vulkan holds these flag arrays as 16 bits. Inter-RPS prediction can code + // a 17th entry, which only a reference set of 16 pictures reaches. + out.use_delta_flag = s.use_delta_flag as u16; + out.abs_delta_rps_minus1 = s.abs_delta_rps_minus1 as u16; + out.used_by_curr_pic_flag = s.used_by_curr_pic_flag as u16; + out.used_by_curr_pic_s0_flag = s.used_by_curr_pic_s0_flag; + out.used_by_curr_pic_s1_flag = s.used_by_curr_pic_s1_flag; + out.num_negative_pics = s.num_negative_pics; + out.num_positive_pics = s.num_positive_pics; + out.delta_poc_s0_minus1 = s.delta_poc_s0_minus1; + out.delta_poc_s1_minus1 = s.delta_poc_s1_minus1; + out +} + +fn long_term_ref_pics(sps: &Sps) -> StdVideoH265LongTermRefPicsSps { + let mut lsb = [0u32; 32]; + for (dst, src) in lsb + .iter_mut() + .zip(&sps.long_term_ref_pics.lt_ref_pic_poc_lsb_sps) + { + *dst = *src; + } + StdVideoH265LongTermRefPicsSps { + used_by_curr_pic_lt_sps_flag: sps.long_term_ref_pics.used_by_curr_pic_lt_sps_flag, + lt_ref_pic_poc_lsb_sps: lsb, + } +} + +fn vui(v: &Vui) -> StdVideoH265SequenceParameterSetVui { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265SequenceParameterSetVui = unsafe { std::mem::zeroed() }; + let f = &mut out.flags; + f.set_aspect_ratio_info_present_flag(v.aspect_ratio_info_present_flag as u32); + f.set_overscan_info_present_flag(v.overscan_info_present_flag as u32); + f.set_overscan_appropriate_flag(v.overscan_appropriate_flag as u32); + f.set_video_signal_type_present_flag(v.video_signal_type_present_flag as u32); + f.set_video_full_range_flag(v.video_full_range_flag as u32); + f.set_colour_description_present_flag(v.colour_description_present_flag as u32); + f.set_chroma_loc_info_present_flag(v.chroma_loc_info_present_flag as u32); + f.set_neutral_chroma_indication_flag(v.neutral_chroma_indication_flag as u32); + f.set_field_seq_flag(v.field_seq_flag as u32); + f.set_frame_field_info_present_flag(v.frame_field_info_present_flag as u32); + f.set_default_display_window_flag(v.default_display_window_flag as u32); + f.set_vui_timing_info_present_flag(v.vui_timing_info_present_flag as u32); + f.set_vui_poc_proportional_to_timing_flag(v.vui_poc_proportional_to_timing_flag as u32); + // The HRD parameters are skipped when parsing, so none are handed over; + // decoding does not use them. + f.set_vui_hrd_parameters_present_flag(0); + f.set_bitstream_restriction_flag(v.bitstream_restriction_flag as u32); + f.set_tiles_fixed_structure_flag(v.tiles_fixed_structure_flag as u32); + f.set_motion_vectors_over_pic_boundaries_flag(v.motion_vectors_over_pic_boundaries_flag as u32); + f.set_restricted_ref_pic_lists_flag(v.restricted_ref_pic_lists_flag as u32); + out.aspect_ratio_idc = v.aspect_ratio_idc as u32; + out.sar_width = v.sar_width; + out.sar_height = v.sar_height; + out.video_format = v.video_format; + out.colour_primaries = v.colour_primaries; + out.transfer_characteristics = v.transfer_characteristics; + out.matrix_coeffs = v.matrix_coeffs; + out.chroma_sample_loc_type_top_field = v.chroma_sample_loc_type_top_field; + out.chroma_sample_loc_type_bottom_field = v.chroma_sample_loc_type_bottom_field; + out.def_disp_win_left_offset = v.def_disp_win_left_offset; + out.def_disp_win_right_offset = v.def_disp_win_right_offset; + out.def_disp_win_top_offset = v.def_disp_win_top_offset; + out.def_disp_win_bottom_offset = v.def_disp_win_bottom_offset; + out.vui_num_units_in_tick = v.vui_num_units_in_tick; + out.vui_time_scale = v.vui_time_scale; + out.vui_num_ticks_poc_diff_one_minus1 = v.vui_num_ticks_poc_diff_one_minus1; + out.min_spatial_segmentation_idc = v.min_spatial_segmentation_idc; + out.max_bytes_per_pic_denom = v.max_bytes_per_pic_denom; + out.max_bits_per_min_cu_denom = v.max_bits_per_min_cu_denom; + out.log2_max_mv_length_horizontal = v.log2_max_mv_length_horizontal; + out.log2_max_mv_length_vertical = v.log2_max_mv_length_vertical; + out +} + +fn video_parameter_set( + vps: &Vps, + ptl: &StdVideoH265ProfileTierLevel, + dpbm: &StdVideoH265DecPicBufMgr, +) -> StdVideoH265VideoParameterSet { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265VideoParameterSet = unsafe { std::mem::zeroed() }; + out.flags + .set_vps_temporal_id_nesting_flag(vps.vps_temporal_id_nesting_flag as u32); + out.flags.set_vps_sub_layer_ordering_info_present_flag( + vps.vps_sub_layer_ordering_info_present_flag as u32, + ); + if let Some(t) = &vps.timing { + out.flags.set_vps_timing_info_present_flag(1); + out.flags + .set_vps_poc_proportional_to_timing_flag(t.poc_proportional_to_timing_flag as u32); + out.vps_num_units_in_tick = t.num_units_in_tick; + out.vps_time_scale = t.time_scale; + out.vps_num_ticks_poc_diff_one_minus1 = t.num_ticks_poc_diff_one_minus1; + } + out.vps_video_parameter_set_id = vps.vps_video_parameter_set_id; + out.vps_max_sub_layers_minus1 = vps.vps_max_sub_layers_minus1; + out.pDecPicBufMgr = dpbm; + out.pProfileTierLevel = ptl; + out +} + +fn sequence_parameter_set(sps: &Sps) -> StdVideoH265SequenceParameterSet { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265SequenceParameterSet = unsafe { std::mem::zeroed() }; + let f = &mut out.flags; + let r = &sps.range_extension; + f.set_sps_temporal_id_nesting_flag(sps.sps_temporal_id_nesting_flag as u32); + f.set_separate_colour_plane_flag(sps.separate_colour_plane_flag as u32); + f.set_conformance_window_flag(sps.conformance_window_flag as u32); + f.set_sps_sub_layer_ordering_info_present_flag( + sps.sps_sub_layer_ordering_info_present_flag as u32, + ); + f.set_scaling_list_enabled_flag(sps.scaling_list_enabled_flag as u32); + f.set_sps_scaling_list_data_present_flag(sps.sps_scaling_list_data_present_flag as u32); + f.set_amp_enabled_flag(sps.amp_enabled_flag as u32); + f.set_sample_adaptive_offset_enabled_flag(sps.sample_adaptive_offset_enabled_flag as u32); + f.set_pcm_enabled_flag(sps.pcm_enabled_flag as u32); + f.set_pcm_loop_filter_disabled_flag(sps.pcm_loop_filter_disabled_flag as u32); + f.set_long_term_ref_pics_present_flag(sps.long_term_ref_pics_present_flag as u32); + f.set_sps_temporal_mvp_enabled_flag(sps.sps_temporal_mvp_enabled_flag as u32); + f.set_strong_intra_smoothing_enabled_flag(sps.strong_intra_smoothing_enabled_flag as u32); + f.set_vui_parameters_present_flag(sps.vui.is_some() as u32); + f.set_sps_extension_present_flag(sps.sps_extension_present_flag as u32); + f.set_sps_range_extension_flag(sps.sps_range_extension_flag as u32); + f.set_transform_skip_rotation_enabled_flag(r.transform_skip_rotation_enabled_flag as u32); + f.set_transform_skip_context_enabled_flag(r.transform_skip_context_enabled_flag as u32); + f.set_implicit_rdpcm_enabled_flag(r.implicit_rdpcm_enabled_flag as u32); + f.set_explicit_rdpcm_enabled_flag(r.explicit_rdpcm_enabled_flag as u32); + f.set_extended_precision_processing_flag(r.extended_precision_processing_flag as u32); + f.set_intra_smoothing_disabled_flag(r.intra_smoothing_disabled_flag as u32); + f.set_high_precision_offsets_enabled_flag(r.high_precision_offsets_enabled_flag as u32); + f.set_persistent_rice_adaptation_enabled_flag(r.persistent_rice_adaptation_enabled_flag as u32); + f.set_cabac_bypass_alignment_enabled_flag(r.cabac_bypass_alignment_enabled_flag as u32); + f.set_sps_scc_extension_flag(sps.sps_scc_extension_flag as u32); + out.chroma_format_idc = sps.chroma_format_idc as u32; + out.pic_width_in_luma_samples = sps.pic_width_in_luma_samples; + out.pic_height_in_luma_samples = sps.pic_height_in_luma_samples; + out.sps_video_parameter_set_id = sps.sps_video_parameter_set_id; + out.sps_max_sub_layers_minus1 = sps.sps_max_sub_layers_minus1; + out.sps_seq_parameter_set_id = sps.sps_seq_parameter_set_id; + out.bit_depth_luma_minus8 = sps.bit_depth_luma_minus8; + out.bit_depth_chroma_minus8 = sps.bit_depth_chroma_minus8; + out.log2_max_pic_order_cnt_lsb_minus4 = sps.log2_max_pic_order_cnt_lsb_minus4; + out.log2_min_luma_coding_block_size_minus3 = sps.log2_min_luma_coding_block_size_minus3; + out.log2_diff_max_min_luma_coding_block_size = sps.log2_diff_max_min_luma_coding_block_size; + out.log2_min_luma_transform_block_size_minus2 = sps.log2_min_luma_transform_block_size_minus2; + out.log2_diff_max_min_luma_transform_block_size = + sps.log2_diff_max_min_luma_transform_block_size; + out.max_transform_hierarchy_depth_inter = sps.max_transform_hierarchy_depth_inter; + out.max_transform_hierarchy_depth_intra = sps.max_transform_hierarchy_depth_intra; + out.num_short_term_ref_pic_sets = sps.short_term_ref_pic_sets.len() as u8; + out.num_long_term_ref_pics_sps = sps.long_term_ref_pics.lt_ref_pic_poc_lsb_sps.len() as u8; + out.pcm_sample_bit_depth_luma_minus1 = sps.pcm_sample_bit_depth_luma_minus1; + out.pcm_sample_bit_depth_chroma_minus1 = sps.pcm_sample_bit_depth_chroma_minus1; + out.log2_min_pcm_luma_coding_block_size_minus3 = sps.log2_min_pcm_luma_coding_block_size_minus3; + out.log2_diff_max_min_pcm_luma_coding_block_size = + sps.log2_diff_max_min_pcm_luma_coding_block_size; + out.conf_win_left_offset = sps.conf_win_left_offset; + out.conf_win_right_offset = sps.conf_win_right_offset; + out.conf_win_top_offset = sps.conf_win_top_offset; + out.conf_win_bottom_offset = sps.conf_win_bottom_offset; + out +} + +fn picture_parameter_set(pps: &Pps, vps_id: u8) -> StdVideoH265PictureParameterSet { + // SAFETY: plain C data, for which all-zero is valid. + let mut out: StdVideoH265PictureParameterSet = unsafe { std::mem::zeroed() }; + let f = &mut out.flags; + let r = &pps.range_extension; + f.set_dependent_slice_segments_enabled_flag(pps.dependent_slice_segments_enabled_flag as u32); + f.set_output_flag_present_flag(pps.output_flag_present_flag as u32); + f.set_sign_data_hiding_enabled_flag(pps.sign_data_hiding_enabled_flag as u32); + f.set_cabac_init_present_flag(pps.cabac_init_present_flag as u32); + f.set_constrained_intra_pred_flag(pps.constrained_intra_pred_flag as u32); + f.set_transform_skip_enabled_flag(pps.transform_skip_enabled_flag as u32); + f.set_cu_qp_delta_enabled_flag(pps.cu_qp_delta_enabled_flag as u32); + f.set_pps_slice_chroma_qp_offsets_present_flag( + pps.pps_slice_chroma_qp_offsets_present_flag as u32, + ); + f.set_weighted_pred_flag(pps.weighted_pred_flag as u32); + f.set_weighted_bipred_flag(pps.weighted_bipred_flag as u32); + f.set_transquant_bypass_enabled_flag(pps.transquant_bypass_enabled_flag as u32); + f.set_tiles_enabled_flag(pps.tiles_enabled_flag as u32); + f.set_entropy_coding_sync_enabled_flag(pps.entropy_coding_sync_enabled_flag as u32); + f.set_uniform_spacing_flag(pps.uniform_spacing_flag as u32); + f.set_loop_filter_across_tiles_enabled_flag(pps.loop_filter_across_tiles_enabled_flag as u32); + f.set_pps_loop_filter_across_slices_enabled_flag( + pps.pps_loop_filter_across_slices_enabled_flag as u32, + ); + f.set_deblocking_filter_control_present_flag(pps.deblocking_filter_control_present_flag as u32); + f.set_deblocking_filter_override_enabled_flag( + pps.deblocking_filter_override_enabled_flag as u32, + ); + f.set_pps_deblocking_filter_disabled_flag(pps.pps_deblocking_filter_disabled_flag as u32); + f.set_pps_scaling_list_data_present_flag(pps.pps_scaling_list_data_present_flag as u32); + f.set_lists_modification_present_flag(pps.lists_modification_present_flag as u32); + f.set_slice_segment_header_extension_present_flag( + pps.slice_segment_header_extension_present_flag as u32, + ); + f.set_pps_extension_present_flag(pps.pps_extension_present_flag as u32); + f.set_cross_component_prediction_enabled_flag(r.cross_component_prediction_enabled_flag as u32); + f.set_chroma_qp_offset_list_enabled_flag(r.chroma_qp_offset_list_enabled_flag as u32); + f.set_pps_range_extension_flag(pps.pps_range_extension_flag as u32); + out.pps_pic_parameter_set_id = pps.pps_pic_parameter_set_id; + out.pps_seq_parameter_set_id = pps.pps_seq_parameter_set_id; + out.sps_video_parameter_set_id = vps_id; + out.num_extra_slice_header_bits = pps.num_extra_slice_header_bits; + out.num_ref_idx_l0_default_active_minus1 = pps.num_ref_idx_l0_default_active_minus1; + out.num_ref_idx_l1_default_active_minus1 = pps.num_ref_idx_l1_default_active_minus1; + out.init_qp_minus26 = pps.init_qp_minus26; + out.diff_cu_qp_delta_depth = pps.diff_cu_qp_delta_depth; + out.pps_cb_qp_offset = pps.pps_cb_qp_offset; + out.pps_cr_qp_offset = pps.pps_cr_qp_offset; + out.pps_beta_offset_div2 = pps.pps_beta_offset_div2; + out.pps_tc_offset_div2 = pps.pps_tc_offset_div2; + out.log2_parallel_merge_level_minus2 = pps.log2_parallel_merge_level_minus2; + out.log2_max_transform_skip_block_size_minus2 = r.log2_max_transform_skip_block_size_minus2; + out.diff_cu_chroma_qp_offset_depth = r.diff_cu_chroma_qp_offset_depth; + out.chroma_qp_offset_list_len_minus1 = r.chroma_qp_offset_list_len_minus1; + out.cb_qp_offset_list = r.cb_qp_offset_list; + out.cr_qp_offset_list = r.cr_qp_offset_list; + out.log2_sao_offset_scale_luma = r.log2_sao_offset_scale_luma; + out.log2_sao_offset_scale_chroma = r.log2_sao_offset_scale_chroma; + out.num_tile_columns_minus1 = pps.num_tile_columns_minus1; + out.num_tile_rows_minus1 = pps.num_tile_rows_minus1; + for (dst, src) in out + .column_width_minus1 + .iter_mut() + .zip(&pps.column_width_minus1) + { + *dst = *src; + } + for (dst, src) in out.row_height_minus1.iter_mut().zip(&pps.row_height_minus1) { + *dst = *src; + } + out +} diff --git a/src/decoder/h265/syntax_tests.rs b/src/decoder/h265/syntax_tests.rs new file mode 100644 index 00000000..f0d50e81 --- /dev/null +++ b/src/decoder/h265/syntax_tests.rs @@ -0,0 +1,229 @@ +//! Slice header syntax that no fixture encoder produces: inter RPS prediction, +//! long-term pictures, and emulation prevention inside the short-term set. +//! The bitstreams are written by hand with a small bit writer. + +use super::parser::{NalType, NalUnit, Reader, parse_nal_header}; +use super::pps::Pps; +use super::rps::parse_st_ref_pic_set; +use super::slice::parse_slice_header; +use super::sps::{LongTermRefPicsSps, Sps}; + +/// Writes RBSP bits and produces an EBSP payload. +#[derive(Default)] +struct BitWriter { + bits: Vec, +} + +impl BitWriter { + fn u(&mut self, n: u32, v: u32) -> &mut Self { + for i in (0..n).rev() { + self.bits.push(v >> i & 1 != 0); + } + self + } + + fn flag(&mut self, v: bool) -> &mut Self { + self.u(1, v as u32) + } + + fn ue(&mut self, v: u32) -> &mut Self { + let code = v as u64 + 1; + let len = 64 - code.leading_zeros(); + self.u(len - 1, 0); + for i in (0..len).rev() { + self.bits.push(code >> i & 1 != 0); + } + self + } + + fn len(&self) -> usize { + self.bits.len() + } + + /// Append rbsp_trailing_bits() and insert emulation prevention bytes. + fn finish(&mut self) -> Vec { + self.flag(true); + while !self.bits.len().is_multiple_of(8) { + self.bits.push(false); + } + let rbsp: Vec = self + .bits + .chunks(8) + .map(|c| c.iter().fold(0u8, |b, bit| b << 1 | *bit as u8)) + .collect(); + let mut out = Vec::new(); + let mut zeros = 0; + for byte in rbsp { + if zeros >= 2 && byte <= 3 { + out.push(3); + zeros = 0; + } + out.push(byte); + zeros = if byte == 0 { zeros + 1 } else { 0 }; + } + out + } +} + +fn nal_bytes(nal_type: NalType, payload: &[u8]) -> Vec { + let mut data = vec![nal_type.raw() << 1, 1]; + data.extend_from_slice(payload); + data +} + +fn base_sps() -> Sps { + let mut sps = Sps { + pic_width_in_luma_samples: 64, + pic_height_in_luma_samples: 64, + log2_max_pic_order_cnt_lsb_minus4: 0, + ..Default::default() + }; + sps.dec_pic_buf_mgr.max_dec_pic_buffering_minus1 = [5; 7]; + sps +} + +/// The leading slice header bits of a P picture up to the short-term set. +fn slice_prefix(w: &mut BitWriter, lsb: u32) { + w.flag(true) // first_slice_segment_in_pic_flag + .ue(0) // slice_pic_parameter_set_id + .ue(1) // slice_type P + .u(4, lsb) + .flag(false); // short_term_ref_pic_set_sps_flag +} + +/// SPS set 0 explicit, set 1 predicted from it; the slice predicts from set +/// 0 with delta_idx_minus1 = 1. Checks the derived sets, the bit count and +/// NumDeltaPocsOfRefRpsIdx. +#[test] +fn inter_rps_prediction_in_sps_and_slice() { + let mut sps = base_sps(); + let mut w = BitWriter::default(); + // Set 0: S0 = {-1, -3}, S1 = {+2}, all used. + w.ue(2) + .ue(1) + .ue(0) + .flag(true) + .ue(1) + .flag(true) + .ue(1) + .flag(true); + // Set 1: inter, deltaRps = -1, all four entries used. + w.flag(true).flag(true).ue(0); + for _ in 0..4 { + w.flag(true); + } + let payload = w.finish(); + let mut r = Reader::new(&payload); + for i in 0..2 { + let set = parse_st_ref_pic_set(&mut r, i, 2, &sps.short_term_ref_pic_sets, 5).unwrap(); + sps.short_term_ref_pic_sets.push(set); + } + let set0 = &sps.short_term_ref_pic_sets[0].rps; + assert_eq!( + (set0.delta_poc_s0.clone(), set0.delta_poc_s1.clone()), + (vec![-1, -3], vec![2]) + ); + let set1 = &sps.short_term_ref_pic_sets[1]; + assert!(set1.syntax.inter_ref_pic_set_prediction_flag); + assert_eq!(set1.rps.delta_poc_s0, vec![-1, -2, -4]); + assert_eq!(set1.rps.delta_poc_s1, vec![1]); + + let mut w = BitWriter::default(); + slice_prefix(&mut w, 5); + let start = w.len(); + w.flag(true) // inter_ref_pic_set_prediction_flag + .ue(1) // delta_idx_minus1: RefRpsIdx = 0 + .flag(false) // delta_rps_sign: deltaRps = +1 + .ue(0); + // used_by_curr_pic_flag / use_delta_flag for j = 0..=3. + w.flag(true); + w.flag(false).flag(true); + w.flag(false).flag(false); + w.flag(true); + let rps_bits = w.len() - start; + let data = nal_bytes(NalType::TrailR, &w.finish()); + let nal = NalUnit { + header: parse_nal_header(&data).unwrap(), + data: &data, + }; + let h = parse_slice_header(&nal, &sps, &Pps::default()).unwrap(); + assert_eq!(h.num_bits_for_st_ref_pic_set as usize, rps_bits); + assert_eq!(rps_bits, 12); + assert_eq!(h.num_delta_pocs_of_ref_rps_idx(&sps), 3); + let set = h.st_ref_pic_set.as_ref().unwrap(); + assert_eq!(set.syntax.delta_idx_minus1, 1); + assert_eq!(set.syntax.used_by_curr_pic_flag, 0b1001); + assert_eq!(set.syntax.use_delta_flag, 0b1011); + assert_eq!(set.rps.delta_poc_s0, vec![-2]); + assert_eq!(set.rps.used_s0, vec![false]); + assert_eq!(set.rps.delta_poc_s1, vec![1]); + assert_eq!(set.rps.used_s1, vec![true]); +} + +/// Long-term entries from the SPS and the slice, with the MSB cycle sum +/// restarting at the first slice-coded entry (equation 7-52). +#[test] +fn long_term_slice_syntax() { + let mut sps = base_sps(); + sps.long_term_ref_pics_present_flag = true; + sps.long_term_ref_pics = LongTermRefPicsSps { + lt_ref_pic_poc_lsb_sps: vec![5, 9, 13], + used_by_curr_pic_lt_sps_flag: 0b010, + }; + sps.sps_temporal_mvp_enabled_flag = true; + + let mut w = BitWriter::default(); + slice_prefix(&mut w, 15); + w.ue(1).ue(0).ue(0).flag(true); // S0 = {-1} + w.ue(1).ue(2); // num_long_term_sps, num_long_term_pics + w.u(2, 1).flag(true).ue(2); // lt_idx_sps 1, msb cycle 2 + w.u(4, 7).flag(false).flag(true).ue(1); // slice entry, msb cycle 1 + w.u(4, 3).flag(true).flag(true).ue(4); // slice entry, msb cycle 4 + w.flag(true); // slice_temporal_mvp_enabled_flag + let data = nal_bytes(NalType::TrailR, &w.finish()); + let nal = NalUnit { + header: parse_nal_header(&data).unwrap(), + data: &data, + }; + let h = parse_slice_header(&nal, &sps, &Pps::default()).unwrap(); + assert_eq!((h.num_long_term_sps, h.num_long_term_pics), (1, 2)); + let lt = &h.long_term_refs; + assert_eq!(lt[0].lt_idx_sps, Some(1)); + assert_eq!((lt[0].poc_lsb_lt, lt[0].used_by_curr_pic_lt), (9, true)); + assert_eq!((lt[1].poc_lsb_lt, lt[1].used_by_curr_pic_lt), (7, false)); + assert_eq!((lt[2].poc_lsb_lt, lt[2].used_by_curr_pic_lt), (3, true)); + let acc: Vec = lt + .iter() + .map(|e| e.delta_poc_msb_cycle_lt_accumulated) + .collect(); + assert_eq!(acc, vec![2, 1, 5]); + assert!(h.slice_temporal_mvp_enabled_flag); +} + +/// Long zero runs inside the short-term set force emulation prevention +/// bytes; the bit count must not include them. +#[test] +fn rps_bit_count_excludes_emulation_prevention() { + let sps = base_sps(); + let mut w = BitWriter::default(); + slice_prefix(&mut w, 0); + let start = w.len(); + w.ue(2).ue(0); + w.ue(32767).flag(false).ue(32767).flag(true); + let rps_bits = w.len() - start; + let payload = w.finish(); + assert!( + payload.windows(3).any(|b| b == [0, 0, 3]), + "the test needs an emulation prevention byte" + ); + let data = nal_bytes(NalType::TrailR, &payload); + let nal = NalUnit { + header: parse_nal_header(&data).unwrap(), + data: &data, + }; + let h = parse_slice_header(&nal, &sps, &Pps::default()).unwrap(); + assert_eq!(h.num_bits_for_st_ref_pic_set as usize, rps_bits); + let set = &h.st_ref_pic_set.as_ref().unwrap().rps; + assert_eq!(set.delta_poc_s0, vec![-32768, -65536]); + assert_eq!(set.used_s0, vec![false, true]); +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 6c29707a..4129d536 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -29,23 +29,44 @@ //! image is valid for exactly as long as the frame is; see [`DecodedFrame`] //! for what holding one costs. //! -//! # Limitations (H.264) +//! # Limitations //! -//! - Progressive streams only (no interlaced/field coding). -//! - Streams using explicit scaling matrices are rejected. -//! -//! Reference management is complete (IDR, sliding-window, and MMCO), so any -//! stream this crate's encoder produces — and typical output from x264, NVENC -//! and friends, including B-pyramid — decodes correctly. - +//! - **H.264**: progressive streams only (no interlaced/field coding), and no +//! explicit scaling matrices. Reference management is complete (IDR, +//! sliding-window, and MMCO), so any stream this crate's encoder produces, +//! and typical output from x264, NVENC and friends, including B-pyramid, +//! decodes correctly. +//! - **H.265**: the base layer only; Main and Main 10 (and 4:2:2 or 4:4:4 where +//! the device has the profile), progressive. The screen content and +//! multi-layer extensions are rejected. RASL pictures after the CRA that +//! decoding starts at are skipped, as the standard allows. +//! - **AV1**: low-overhead OBU streams (the format of `.obu` files, IVF and +//! MP4), not Annex B; operating point 0. Monochrome streams and 12-bit are +//! rejected. Film grain is applied, which takes a second image for every DPB +//! slot. A device whose DPB and output images do not coincide is not +//! supported. + +/// Annex B start-code framing, shared by H.264 and H.265. +pub(crate) mod annexb; +/// AV1 decoding. +pub(crate) mod av1; pub(crate) mod bitreader; /// The Vulkan video session and the per-decoder state every codec shares. pub(crate) mod common; +/// The decode flow every codec shares. +pub(crate) mod driver; /// Frame ownership: pool images, DPB slot pins, display-order reordering. pub(crate) mod frames; /// H.264 decoding. pub(crate) mod h264; +/// H.265 decoding. +pub(crate) mod h265; pub(crate) mod pipeline; +/// Turning what a stream needs into a session plan. +pub(crate) mod plan; +pub(crate) mod profile; +/// The structures a decode command is recorded from. +pub(crate) mod record; /// Readback and copies, all of which run on the transfer queue. pub(crate) mod transfer; @@ -113,10 +134,10 @@ pub struct DecodeConfig { } impl DecodeConfig { - /// Create an H.264 decode configuration. - pub fn h264() -> Self { + /// Create a decode configuration for `codec`. + pub fn new(codec: Codec) -> Self { Self { - codec: Codec::H264, + codec, output_depth: DEFAULT_OUTPUT_DEPTH, consumer_queue_family: None, framing: Framing::FrameAligned, @@ -398,13 +419,18 @@ impl Decoder { ))); } let mut inner: Box = match config.codec { - Codec::H264 => Box::new(h264::H264Decoder::create(context, &config)?), - other => { - return Err(PixelForgeError::InvalidInput(format!( - "{:?} decoding is not implemented yet", - other - ))); - } + Codec::H264 => Box::new(driver::StreamDecoder::new( + h264::H264Decoder::create(context, &config)?, + &config, + )), + Codec::H265 => Box::new(driver::StreamDecoder::new( + h265::H265Decoder::create(context, &config)?, + &config, + )), + Codec::AV1 => Box::new(driver::StreamDecoder::new( + av1::Av1Decoder::create(context, &config)?, + &config, + )), }; let rx = inner .take_frame_receiver() diff --git a/src/decoder/plan.rs b/src/decoder/plan.rs new file mode 100644 index 00000000..a1d733f9 --- /dev/null +++ b/src/decoder/plan.rs @@ -0,0 +1,335 @@ +//! Planning a decode session: from what a stream needs and what the device +//! offers for its profile to the numbers a session is built from. +//! +//! The codec reads its parameter sets and fills a [`StreamNeeds`]. Everything +//! after that (capabilities, DPB slots, picture format, image flags) is the +//! same for every codec and lives here. + +use ash::vk; +use ash::vk::TaggedStructure; +use tracing::debug; + +use crate::decoder::common::DecoderCommon; +use crate::encoder::{BitDepth, ChromaFormat}; +use crate::error::{PixelForgeError, Result}; +use crate::video::{align_up, get_video_format, query_supported_video_formats}; + +/// What a stream asks of a decode session, as the codec reads it from the +/// stream's own parameter sets. +pub(crate) struct StreamNeeds { + /// The codec's name, for messages: "H.264". + pub codec: &'static str, + /// Coded dimensions before alignment to the device's picture granularity. + pub width: u32, + pub height: u32, + pub chroma_format: ChromaFormat, + pub bit_depth: BitDepth, + /// Reference pictures the stream keeps in its DPB at once. + pub references: u32, + /// Reference pictures a single picture may use. Never more than + /// `references`. + pub active_references: u32, + /// The most DPB slots the codec can address. + pub slot_limit: u32, + /// Pictures the stream may hold back for display-order reordering. + pub reorder_depth: usize, + /// Frames the caller may hold at once (`DecodeConfig::output_depth`). + pub output_depth: usize, + /// The stream applies film grain (AV1), which needs an output image apart + /// from the DPB slot for every slot. + pub film_grain: bool, +} + +/// What a codec decided the session should look like. +/// +/// [`DecoderCommon::plan_session`] derives these from the stream's needs and +/// the device caps; [`DecoderCommon::create_session`] turns them into Vulkan +/// objects. +pub(crate) struct SessionPlan { + /// The codec's name, for messages. + pub codec: &'static str, + /// Coded dimensions, already aligned to the device's picture granularity. + pub coded_width: u32, + pub coded_height: u32, + pub picture_format: vk::Format, + pub bit_depth: BitDepth, + pub chroma_format: ChromaFormat, + /// DPB slots: references, the picture being decoded, and `spare_slots`. + pub slot_count: usize, + /// How many of `slot_count` are spare capacity for pictures pinned past the + /// decode that produced them: those held for display-order reordering, and + /// those handed to the caller. Zero means every emitted picture has to be + /// copied out instead of pinned in place. + pub spare_slots: usize, + /// References the driver may use for a single picture. + pub max_active_references: u32, + /// Driver decodes straight into the DPB image (`DPB_AND_OUTPUT_COINCIDE`). + pub coincide: bool, + /// A decoded picture may be handed to the caller in place, pinned in its + /// DPB slot, rather than copied out. See + /// [`DecodeSession::pinnable`](crate::decoder::common::DecodeSession::pinnable). + pub pinnable: bool, + /// `dpb_usage` includes `SAMPLED`, so handed-out frames can be sampled. + pub sampleable: bool, + /// Picture images are created with `MUTABLE_FORMAT`, so a consumer can view + /// their planes separately. See + /// [`DecodeSession::plane_views`](crate::decoder::common::DecodeSession::plane_views). + pub plane_views: bool, + /// Creation flags for picture images, from what the driver reported. + pub image_flags: vk::ImageCreateFlags, + /// Formats views of a picture image may use. Empty unless `plane_views`. + pub view_formats: Vec, + /// One layered DPB image rather than one image per slot. + pub use_layered_dpb: bool, + pub dpb_usage: vk::ImageUsageFlags, + /// Create a film grain output image for every DPB slot. + pub film_grain: bool, + /// The Std header version the device reports for this profile. + pub std_header_version: vk::ExtensionProperties, + /// Bitstream buffer alignments the device requires, each at least one. + pub bitstream_offset_alignment: u64, + pub bitstream_size_alignment: u64, +} + +/// The fields of `VkVideoCapabilitiesKHR` a plan uses, copied out of the +/// query's pointer chain. +struct Caps { + flags: vk::VideoCapabilityFlagsKHR, + min_bitstream_buffer_offset_alignment: u64, + min_bitstream_buffer_size_alignment: u64, + picture_access_granularity: vk::Extent2D, + max_coded_extent: vk::Extent2D, + max_dpb_slots: u32, + max_active_reference_pictures: u32, + std_header_version: vk::ExtensionProperties, +} + +impl DecoderCommon { + /// Plan a session for a stream with `needs`, decoded with `profile_info`. + /// + /// `codec_caps` is the codec's capability struct (for example + /// `VkVideoDecodeH264CapabilitiesKHR`), which the query must chain even + /// when the codec reads nothing from it. + pub fn plan_session<'a, C>( + &self, + profile_info: &vk::VideoProfileInfoKHR, + codec_caps: &'a mut C, + needs: &StreamNeeds, + ) -> Result + where + C: vk::Extends> + TaggedStructure<'a>, + { + let codec = needs.codec; + + // Query capabilities for this exact profile. The chain borrows the + // caps structs, so scope it and copy the results out. + let mut decode_caps = vk::VideoDecodeCapabilitiesKHR::default(); + let caps = { + let mut chain = vk::VideoCapabilitiesKHR::default() + .push(codec_caps) + .push(&mut decode_caps); + query_decode_caps(self, profile_info, &mut chain)?; + Caps { + flags: chain.flags, + min_bitstream_buffer_offset_alignment: chain.min_bitstream_buffer_offset_alignment, + min_bitstream_buffer_size_alignment: chain.min_bitstream_buffer_size_alignment, + picture_access_granularity: chain.picture_access_granularity, + max_coded_extent: chain.max_coded_extent, + max_dpb_slots: chain.max_dpb_slots, + max_active_reference_pictures: chain.max_active_reference_pictures, + std_header_version: chain.std_header_version, + } + }; + let decode_flags = decode_caps.flags; + + let granularity = caps.picture_access_granularity; + let coded_width = align_up(needs.width, granularity.width.max(1)); + let coded_height = align_up(needs.height, granularity.height.max(1)); + let max_extent = caps.max_coded_extent; + if coded_width > max_extent.width || coded_height > max_extent.height { + return Err(PixelForgeError::InvalidInput(format!( + "{codec} decode: stream is {}x{}, device maximum is {}x{}", + coded_width, coded_height, max_extent.width, max_extent.height + ))); + } + + let coincide = + decode_flags.contains(vk::VideoDecodeCapabilityFlagsKHR::DPB_AND_OUTPUT_COINCIDE); + debug!("{codec} decode capability flags: {:?}", decode_flags); + let use_layered_dpb = !caps + .flags + .contains(vk::VideoCapabilityFlagsKHR::SEPARATE_REFERENCE_IMAGES); + + // The stream needs its references plus the current picture. On top of + // that come the spare slots, which is what lets a picture stay pinned + // in place past the decode that produced it instead of being copied + // out: `reorder_depth` of them for pictures waiting their turn in + // display order, and `output_depth` for frames the caller is holding. + // The device caps bound the total. + let required = (needs.references + 1).max(2); + let slot_limit = caps.max_dpb_slots.min(needs.slot_limit); + if required > slot_limit { + return Err(PixelForgeError::InvalidInput(format!( + "{codec} decode: stream needs {} DPB slots, device supports {}", + required, caps.max_dpb_slots + ))); + } + // Spare slots only buy something when pictures can be pinned. Without + // that they are handed out as copies, which needs no DPB slot at all, + // so asking for spares would just reserve memory nothing uses. + let pinnable = coincide && self.context.has_unified_image_layouts(); + let wanted_spare = if pinnable { + needs.reorder_depth + needs.output_depth + } else { + 0 + }; + let slot_count = (required + wanted_spare as u32).min(slot_limit) as usize; + let spare_slots = slot_count - required as usize; + if spare_slots < wanted_spare { + debug!( + "{codec} decode: {} of {} spare DPB slots available \ + (reorder depth {} plus {} output slots; the stream's references \ + need {} of the device's {}); pictures beyond that are copied", + spare_slots, + wanted_spare, + needs.reorder_depth, + needs.output_depth, + required, + caps.max_dpb_slots + ); + } + + // Only the reference pictures are ever active at once: the current + // picture and the output reservation are not references. + let active_references = needs.active_references.max(1).min(required - 1); + if active_references > caps.max_active_reference_pictures { + return Err(PixelForgeError::InvalidInput(format!( + "{codec} decode: stream needs {} active reference pictures, device supports {}", + active_references, caps.max_active_reference_pictures + ))); + } + + // Pick a picture format the device actually supports for decode. + // + // Ask for SAMPLED as well: a coincident DPB image is what the caller + // receives, and SAMPLED is what lets them read it in a shader instead + // of copying it out first. Fall back to the plain usage if the device + // reports no sampleable format, which costs the caller a copy but keeps + // decoding working. With a distinct output image nothing asks, since + // those pictures reach the caller as pool copies, which are sampleable + // whatever the device says about video images. + let base_usage = if coincide { + vk::ImageUsageFlags::VIDEO_DECODE_DPB_KHR + | vk::ImageUsageFlags::VIDEO_DECODE_DST_KHR + | vk::ImageUsageFlags::TRANSFER_SRC + } else { + vk::ImageUsageFlags::VIDEO_DECODE_DPB_KHR + }; + let sampled_usage = base_usage | vk::ImageUsageFlags::SAMPLED; + let sampled_formats = if coincide { + query_supported_video_formats(&self.context, profile_info, sampled_usage) + .unwrap_or_default() + } else { + Vec::new() + }; + let (dpb_usage, sampleable, supported) = if sampled_formats.is_empty() { + let formats = query_supported_video_formats(&self.context, profile_info, base_usage)?; + (base_usage, false, formats) + } else { + (sampled_usage, true, sampled_formats) + }; + let preferred = get_video_format(needs.chroma_format, needs.bit_depth); + let chosen = supported + .iter() + .find(|f| f.format == preferred) + .or_else(|| supported.first()) + .copied() + .ok_or_else(|| { + PixelForgeError::NoSuitableDevice(format!( + "{codec} decode: no supported picture format for this profile" + )) + })?; + let picture_format = chosen.format; + + // MUTABLE_FORMAT lets a consumer view the picture's planes separately, + // which is the only way to read a multi-planar image without a + // sampler-YCbCr conversion, and the only way at all for a renderer + // whose shaders come from a toolchain that cannot express one (naga and + // therefore wgpu, among others). It is opportunistic: the driver + // enumerates which creation flags it accepts for this profile, format + // and usage, and DPB-only images on both RADV and ANV accept none. + // + // Naming the plane formats keeps a driver from having to assume the + // image might be reinterpreted as anything, which is what would + // otherwise cost it compression. + let plane_views = chosen + .create_flags + .contains(vk::ImageCreateFlags::MUTABLE_FORMAT); + let image_flags = if plane_views { + vk::ImageCreateFlags::MUTABLE_FORMAT + } else { + vk::ImageCreateFlags::empty() + }; + let view_formats: Vec = if plane_views { + let mut list = vec![picture_format]; + list.extend(crate::video::plane_view_formats(picture_format)); + list + } else { + Vec::new() + }; + + Ok(SessionPlan { + codec, + coded_width, + coded_height, + picture_format, + bit_depth: needs.bit_depth, + chroma_format: needs.chroma_format, + slot_count, + spare_slots, + max_active_references: active_references, + coincide, + pinnable, + sampleable, + plane_views, + image_flags, + view_formats, + use_layered_dpb, + dpb_usage, + film_grain: needs.film_grain, + std_header_version: caps.std_header_version, + bitstream_offset_alignment: caps.min_bitstream_buffer_offset_alignment.max(1), + bitstream_size_alignment: caps.min_bitstream_buffer_size_alignment.max(1), + }) + } +} + +/// Query decode capabilities for a profile. +/// +/// `caps` is supplied by the caller already chained with the codec-specific +/// capability struct, since only the codec knows which one applies. +fn query_decode_caps( + common: &DecoderCommon, + profile_info: &vk::VideoProfileInfoKHR, + caps: &mut vk::VideoCapabilitiesKHR, +) -> Result<()> { + let context = &common.context; + let video_queue_instance = + ash::khr::video_queue::Instance::load(context.entry(), context.instance()); + let result = unsafe { + (video_queue_instance + .fp() + .get_physical_device_video_capabilities_khr)( + context.physical_device(), + profile_info, + caps, + ) + }; + if result != vk::Result::SUCCESS { + return Err(PixelForgeError::NoSuitableDevice(format!( + "decode profile not supported: {:?}", + result + ))); + } + Ok(()) +} diff --git a/src/decoder/profile.rs b/src/decoder/profile.rs new file mode 100644 index 00000000..c50c20ba --- /dev/null +++ b/src/decoder/profile.rs @@ -0,0 +1,117 @@ +//! The video profile a decode session uses. + +use ash::vk; + +use crate::encoder::{BitDepth, ChromaFormat, Codec}; + +/// The video profile a decode session uses, owned so it can be named again +/// after creation. +/// +/// The session, its DPB images, its output image and every bitstream buffer +/// it reads have to name the session's exact profile. The chain lives in one +/// heap allocation, so the pointer from the base struct to the codec struct +/// stays valid for as long as this does. +pub(crate) struct DecodeProfile(Box); + +struct ProfileChain { + info: vk::VideoProfileInfoKHR<'static>, + h264: vk::VideoDecodeH264ProfileInfoKHR<'static>, + h265: vk::VideoDecodeH265ProfileInfoKHR<'static>, + av1: vk::VideoDecodeAV1ProfileInfoKHR<'static>, +} + +// SAFETY: the raw pointer in the chain only points into the same boxed +// allocation, which moves with this value and is never shared. +unsafe impl Send for DecodeProfile {} + +impl DecodeProfile { + /// The profile that says whether a device decodes `codec` at all: 8-bit + /// 4:2:0 in H.264 High, H.265 Main or AV1 Main. + pub(crate) fn base(codec: Codec) -> Self { + let (chroma, depth) = (ChromaFormat::Yuv420, BitDepth::Eight); + match codec { + Codec::H264 => Self::h264( + ash::vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH, + chroma, + depth, + ), + Codec::H265 => Self::h265( + ash::vk::native::StdVideoH265ProfileIdc_STD_VIDEO_H265_PROFILE_IDC_MAIN, + chroma, + depth, + ), + Codec::AV1 => Self::av1( + ash::vk::native::StdVideoAV1Profile_STD_VIDEO_AV1_PROFILE_MAIN, + false, + chroma, + depth, + ), + } + } + + /// An H.264 decode profile. + pub(crate) fn h264( + std_profile_idc: ash::vk::native::StdVideoH264ProfileIdc, + chroma_format: ChromaFormat, + bit_depth: BitDepth, + ) -> Self { + let mut chain = Self::chain(Codec::H264, chroma_format, bit_depth); + chain.h264 = vk::VideoDecodeH264ProfileInfoKHR::default() + .std_profile_idc(std_profile_idc) + .picture_layout(vk::VideoDecodeH264PictureLayoutFlagsKHR::PROGRESSIVE); + chain.info.p_next = (&chain.h264 as *const vk::VideoDecodeH264ProfileInfoKHR).cast(); + Self(chain) + } + + /// An H.265 decode profile. + pub(crate) fn h265( + std_profile_idc: ash::vk::native::StdVideoH265ProfileIdc, + chroma_format: ChromaFormat, + bit_depth: BitDepth, + ) -> Self { + let mut chain = Self::chain(Codec::H265, chroma_format, bit_depth); + chain.h265 = vk::VideoDecodeH265ProfileInfoKHR::default().std_profile_idc(std_profile_idc); + chain.info.p_next = (&chain.h265 as *const vk::VideoDecodeH265ProfileInfoKHR).cast(); + Self(chain) + } + + /// An AV1 decode profile. `film_grain` asks for a session that can apply + /// film grain to the output. + pub(crate) fn av1( + std_profile: ash::vk::native::StdVideoAV1Profile, + film_grain: bool, + chroma_format: ChromaFormat, + bit_depth: BitDepth, + ) -> Self { + let mut chain = Self::chain(Codec::AV1, chroma_format, bit_depth); + chain.av1 = vk::VideoDecodeAV1ProfileInfoKHR::default() + .std_profile(std_profile) + .film_grain_support(film_grain); + chain.info.p_next = (&chain.av1 as *const vk::VideoDecodeAV1ProfileInfoKHR).cast(); + Self(chain) + } + + fn chain(codec: Codec, chroma_format: ChromaFormat, bit_depth: BitDepth) -> Box { + let operation = match codec { + Codec::H264 => vk::VideoCodecOperationFlagsKHR::DECODE_H264, + Codec::H265 => vk::VideoCodecOperationFlagsKHR::DECODE_H265, + Codec::AV1 => vk::VideoCodecOperationFlagsKHR::DECODE_AV1, + }; + let depth: vk::VideoComponentBitDepthFlagsKHR = bit_depth.into(); + Box::new(ProfileChain { + info: vk::VideoProfileInfoKHR::default() + .video_codec_operation(operation) + .chroma_subsampling(chroma_format.into()) + .luma_bit_depth(depth) + .chroma_bit_depth(depth), + h264: vk::VideoDecodeH264ProfileInfoKHR::default(), + h265: vk::VideoDecodeH265ProfileInfoKHR::default(), + av1: vk::VideoDecodeAV1ProfileInfoKHR::default(), + }) + } + + /// The profile, with its chain. + pub(crate) fn info(&self) -> &vk::VideoProfileInfoKHR<'_> { + &self.0.info + } +} diff --git a/src/decoder/record.rs b/src/decoder/record.rs new file mode 100644 index 00000000..485f7882 --- /dev/null +++ b/src/decoder/record.rs @@ -0,0 +1,287 @@ +//! Storage for the structures one decode command is recorded from. +//! +//! NVIDIA's decoder does not copy `VkVideoDecodeInfoKHR` and the structures +//! hanging off it when the command is recorded: it reads them again at +//! `vkQueueSubmit`. They therefore have to stay alive and unmoved until the +//! submission has finished, which a stack frame in the recording function does +//! not guarantee. Left on the stack they are garbage by submit time, which +//! crashed the driver or made it decode B-pictures from stale reference data. +//! +//! A [`DecodeRecord`] owns all of them. There is one per pipeline slot, and it +//! is rebuilt only once that slot's previous submission has completed. The +//! structures every codec shares live here; a [`RecordCodec`] supplies the +//! codec's own. + +use ash::vk; +use ash::vk::TaggedStructure; + +/// Most references one picture can have: every DPB slot but the one being set +/// up, for the codec with the most slots (H.264 and H.265, 16 references). +const MAX_REFS: usize = 17; + +/// The codec-specific structures of a decode command. +pub(crate) trait RecordCodec { + /// The Std reference info of one DPB slot. + type RefStd: Copy + 'static; + /// The codec's `Vk...DpbSlotInfoKHR`, which points at a [`Self::RefStd`]. + type DpbSlot: Copy + + Default + + 'static + + vk::Extends> + + TaggedStructure<'static>; + /// What the codec's picture info points at: the Std picture info and + /// anything it points at in turn. Kept in the record, so it stays in place + /// until the submission has completed. + type Picture: 'static; + /// The codec's `Vk...PictureInfoKHR`. + type PictureInfo: Default + + 'static + + vk::Extends> + + TaggedStructure<'static>; + + /// A DPB slot info pointing at `std`. + fn dpb_slot(std: &'static Self::RefStd) -> Self::DpbSlot; + + /// The picture info for `picture`, whose slices or tiles start at + /// `offsets` in the bitstream buffer. + /// + /// May also wire the pointers inside `picture` itself. + fn picture_info( + picture: &'static mut Self::Picture, + offsets: &'static [u32], + ) -> Self::PictureInfo; +} + +/// Everything one decode command points at. +/// +/// The Vulkan structures here point at each other and at the plain data beside +/// them, so the whole is a self-referential value. That is sound because it lives in +/// heap storage it is never moved out of once built, and every pointer is rebuilt +/// by [`DecodeRecord::link`] after the last write to what it points at. The +/// `'static` lifetimes on the fields are internal: nothing outside this module +/// can obtain a reference that outlives the record. +/// +/// Every collection is a fixed-size array holding `ref_count` live entries, so +/// nothing is ever reallocated and an element's address does not change however +/// the record is filled. +pub(crate) struct DecodeRecord { + // Plain data. + offsets: Vec, + ref_count: usize, + /// DPB slot, image view and coded extent of each reference. A `None` + /// extent is the picture's own. + refs: [(u8, vk::ImageView, Option); MAX_REFS], + ref_std: [C::RefStd; MAX_REFS], + setup_std: C::RefStd, + picture: C::Picture, + + // Picture resources. + ref_resources: [vk::VideoPictureResourceInfoKHR<'static>; MAX_REFS], + setup_resource: vk::VideoPictureResourceInfoKHR<'static>, + output_resource: vk::VideoPictureResourceInfoKHR<'static>, + + // Codec-specific slot info. The begin and decode commands each chain their + // own copy, since chaining takes the structure mutably. + begin_dpb: [C::DpbSlot; MAX_REFS], + decode_dpb: [C::DpbSlot; MAX_REFS], + setup_dpb: C::DpbSlot, + + // Reference slots. `begin_slots` has room for the slot being set up too. + begin_slots: [vk::VideoReferenceSlotInfoKHR<'static>; MAX_REFS + 1], + decode_slots: [vk::VideoReferenceSlotInfoKHR<'static>; MAX_REFS], + setup_slot: vk::VideoReferenceSlotInfoKHR<'static>, + + // Top level. + pic_info: C::PictureInfo, + begin_info: vk::VideoBeginCodingInfoKHR<'static>, + decode_info: vk::VideoDecodeInfoKHR<'static>, +} + +// SAFETY: the raw pointers inside the Vulkan structures only ever point into +// the same record, which is owned by one decoder and used from one thread at a +// time. +unsafe impl Send for DecodeRecord {} + +/// What [`DecodeRecord::link`] needs besides the references already pushed. +pub(crate) struct LinkParams { + pub extent: vk::Extent2D, + pub session: vk::VideoSessionKHR, + pub session_params: vk::VideoSessionParametersKHR, + pub bitstream: vk::Buffer, + pub bitstream_range: u64, + /// DPB slot, and view of it, that the picture is reconstructed into. + pub setup_slot: u8, + pub setup_view: vk::ImageView, + /// The decode destination when it is not the DPB slot itself. + pub output_view: Option, + pub setup_std: C::RefStd, +} + +/// Extend a borrow to any lifetime. +/// +/// # Safety +/// `value` must stay alive and in place for as long as the result is used. +unsafe fn stable<'a, T: ?Sized>(value: &T) -> &'a T { + unsafe { &*(value as *const T) } +} + +/// As [`stable`], for a mutable borrow. +/// +/// # Safety +/// As [`stable`], and nothing else may access `value` while the result is used. +unsafe fn stable_mut<'a, T: ?Sized>(value: &mut T) -> &'a mut T { + unsafe { &mut *(value as *mut T) } +} + +impl DecodeRecord { + /// An empty record. `picture` is the codec's picture storage, which + /// [`Self::link`] overwrites. + pub(crate) fn new(picture: C::Picture) -> Self { + // SAFETY: the standard structures are plain C data, for which all-zero + // is a valid (if meaningless) value. + let zeroed_ref: C::RefStd = unsafe { std::mem::zeroed() }; + Self { + offsets: Vec::new(), + ref_count: 0, + refs: [(0, vk::ImageView::null(), None); MAX_REFS], + ref_std: [zeroed_ref; MAX_REFS], + setup_std: zeroed_ref, + picture, + ref_resources: [Default::default(); MAX_REFS], + setup_resource: Default::default(), + output_resource: Default::default(), + begin_dpb: [Default::default(); MAX_REFS], + decode_dpb: [Default::default(); MAX_REFS], + setup_dpb: Default::default(), + begin_slots: [Default::default(); MAX_REFS + 1], + decode_slots: [Default::default(); MAX_REFS], + setup_slot: Default::default(), + pic_info: Default::default(), + begin_info: Default::default(), + decode_info: Default::default(), + } + } + + /// Start a new picture, discarding the last one. + /// + /// Only call once the submission that used this record has completed. + /// `offsets` are where the picture's slices or tiles start in the + /// bitstream buffer. + pub(crate) fn reset(&mut self, offsets: &[u32]) { + self.offsets.clear(); + self.offsets.extend_from_slice(offsets); + self.ref_count = 0; + } + + /// The codec's picture storage, to fill before [`Self::link`]. + pub(crate) fn picture_mut(&mut self) -> &mut C::Picture { + &mut self.picture + } + + /// Add a reference picture, in the order the driver is to see it. + /// + /// `extent` is the reference's coded extent when it differs from the + /// picture's own, which only AV1 allows (a frame size change between key + /// frames). + pub(crate) fn push_reference( + &mut self, + slot: u8, + view: vk::ImageView, + extent: Option, + std: C::RefStd, + ) { + assert!(self.ref_count < MAX_REFS, "too many references"); + self.refs[self.ref_count] = (slot, view, extent); + self.ref_std[self.ref_count] = std; + self.ref_count += 1; + } + + /// Build the structures from what has been pushed, wiring up every pointer. + /// + /// Order matters: the plain data and the structures pointed at are written + /// before the structures that point at them. + pub(crate) fn link(&mut self, p: &LinkParams) { + let n = self.ref_count; + self.setup_std = p.setup_std; + + let resource = |view: vk::ImageView, extent: vk::Extent2D| { + vk::VideoPictureResourceInfoKHR::default() + .coded_offset(vk::Offset2D { x: 0, y: 0 }) + .coded_extent(extent) + .base_array_layer(0) + .image_view_binding(view) + }; + for i in 0..n { + let (_, view, extent) = self.refs[i]; + self.ref_resources[i] = resource(view, extent.unwrap_or(p.extent)); + } + self.setup_resource = resource(p.setup_view, p.extent); + self.output_resource = match p.output_view { + None => self.setup_resource, + Some(view) => resource(view, p.extent), + }; + + // SAFETY (for every `stable`/`stable_mut` below): the target is a field + // of `self`, which is boxed and not written again until the next + // `reset`/`link`, and fixed arrays never move their elements. + unsafe { + for i in 0..n { + let info = C::dpb_slot(stable(&self.ref_std[i])); + self.begin_dpb[i] = info; + self.decode_dpb[i] = info; + } + self.setup_dpb = C::dpb_slot(stable(&self.setup_std)); + + for i in 0..n { + let slot = self.refs[i].0 as i32; + let resource = stable(&self.ref_resources[i]); + self.begin_slots[i] = vk::VideoReferenceSlotInfoKHR::default() + .slot_index(slot) + .picture_resource(resource) + .push(stable_mut(&mut self.begin_dpb[i])); + self.decode_slots[i] = vk::VideoReferenceSlotInfoKHR::default() + .slot_index(slot) + .picture_resource(resource) + .push(stable_mut(&mut self.decode_dpb[i])); + } + // The slot being reconstructed is listed to `begin` with slot index + // -1: it is not active until the decode's setup slot activates it, + // and a non-negative index here must name an already-active slot + // (VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239). -1 still binds + // its picture resource. + self.begin_slots[n] = vk::VideoReferenceSlotInfoKHR::default() + .slot_index(-1) + .picture_resource(stable(&self.setup_resource)); + self.setup_slot = vk::VideoReferenceSlotInfoKHR::default() + .slot_index(p.setup_slot as i32) + .picture_resource(stable(&self.setup_resource)) + .push(stable_mut(&mut self.setup_dpb)); + + self.pic_info = + C::picture_info(stable_mut(&mut self.picture), stable(&self.offsets[..])); + + self.begin_info = vk::VideoBeginCodingInfoKHR::default() + .video_session(p.session) + .video_session_parameters(p.session_params) + .reference_slots(stable(&self.begin_slots[..n + 1])); + self.decode_info = vk::VideoDecodeInfoKHR::default() + .src_buffer(p.bitstream) + .src_buffer_offset(0) + .src_buffer_range(p.bitstream_range) + .dst_picture_resource(self.output_resource) + .setup_reference_slot(stable(&self.setup_slot)) + .reference_slots(stable(&self.decode_slots[..n])) + .push(stable_mut(&mut self.pic_info)); + } + } + + /// The `vkCmdBeginVideoCodingKHR` argument. Valid after [`Self::link`]. + pub(crate) fn begin_info(&self) -> &vk::VideoBeginCodingInfoKHR<'_> { + &self.begin_info + } + + /// The `vkCmdDecodeVideoKHR` argument. Valid after [`Self::link`]. + pub(crate) fn decode_info(&self) -> &vk::VideoDecodeInfoKHR<'_> { + &self.decode_info + } +} diff --git a/src/lib.rs b/src/lib.rs index 6378cd6d..ec1f8a80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,10 @@ //! A Vulkan-based video encoding and decoding library for Rust, supporting H.264, -//! H.265 and AV1 encode, and H.264 decode. +//! H.265 and AV1, for both encode and decode. //! //! # Features //! //! - **Hardware-accelerated** video encoding and decoding using Vulkan Video extensions. -//! - **Multiple codec support**: H.264/AVC, H.265/HEVC, AV1 encode; H.264 decode. +//! - **Multiple codec support**: H.264/AVC, H.265/HEVC and AV1, encode and decode. //! - **Asynchronous pipelines**: both directions submit without waiting. //! Encoding hands back an [`EncodeFuture`]; decoding delivers frames through a //! [`DecodeSource`] as the GPU finishes with them. @@ -31,11 +31,12 @@ //! | Codec | Encode | Decode | //! |-------|--------|--------| //! | H.264/AVC | ✓ | ✓ | -//! | H.265/HEVC | ✓ | | -//! | AV1 | ✓ | | +//! | H.265/HEVC | ✓ | ✓ | +//! | AV1 | ✓ | ✓ | //! //! H.264 decoding is verified byte-identical to `ffmpeg -pix_fmt nv12` on AMD -//! (RADV), NVIDIA and Intel (ANV). +//! (RADV), NVIDIA and Intel (ANV). H.265 decoding is verified byte-identical to +//! ffmpeg, and AV1 decoding (film grain included) to dav1d, on NVIDIA so far. //! //! # Requirements //! @@ -146,7 +147,7 @@ //! //! // A file can cut anywhere, so let the decoder frame it. Input that //! // arrives already framed (RTP, a container) skips `with_byte_stream`. -//! let config = DecodeConfig::h264().with_byte_stream(); +//! let config = DecodeConfig::new(Codec::H264).with_byte_stream(); //! let mut decoder = Decoder::new(context, config)?; //! let stream: Vec = std::fs::read("input.264")?; //! diff --git a/src/vulkan.rs b/src/vulkan.rs index 66e2709c..f97d385e 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -1,4 +1,5 @@ //! Vulkan context and initialization for video encoding. +use crate::decoder::profile::DecodeProfile; use crate::encoder::Codec; use crate::error::{PixelForgeError, Result}; use ash::vk; @@ -564,23 +565,100 @@ fn decode_extension_names(decode_codecs: &[Codec]) -> Vec<&'static std::ffi::CSt ash::khr::synchronization2::NAME, ash::khr::timeline_semaphore::NAME, ]; - if decode_codecs.contains(&Codec::H264) { - names.push(ash::khr::video_decode_h264::NAME); + for codec in decode_codecs { + names.push(decode_extension_name(*codec)); } names } +/// The device extension that decodes `codec`. +fn decode_extension_name(codec: Codec) -> &'static std::ffi::CStr { + match codec { + Codec::H264 => ash::khr::video_decode_h264::NAME, + Codec::H265 => ash::khr::video_decode_h265::NAME, + Codec::AV1 => ash::khr::video_decode_av1::NAME, + } +} + +/// The codecs pixelforge can decode, on a device that supports them. +const DECODE_CODECS: &[Codec] = &[Codec::H264, Codec::H265, Codec::AV1]; + /// The decode codecs `physical_device` supports. fn query_decode_codecs( entry: &ash::Entry, instance: &ash::Instance, physical_device: vk::PhysicalDevice, ) -> Vec { - let mut codecs = Vec::new(); - if VideoContext::check_h264_decode_support(entry, instance, physical_device) { - codecs.push(Codec::H264); + let extensions = unsafe { instance.enumerate_device_extension_properties(physical_device) } + .unwrap_or_default(); + let has_extension = |name: &std::ffi::CStr| { + extensions + .iter() + .any(|ext| ext.extension_name_as_c_str() == Ok(name)) + }; + DECODE_CODECS + .iter() + .copied() + .filter(|&codec| { + has_extension(decode_extension_name(codec)) + && check_decode_support(entry, instance, physical_device, codec) + }) + .collect() +} + +/// Whether `physical_device` decodes `codec` in its base profile: 8-bit +/// 4:2:0, H.264 High, H.265 Main or AV1 Main. +fn check_decode_support( + entry: &ash::Entry, + instance: &ash::Instance, + physical_device: vk::PhysicalDevice, + codec: Codec, +) -> bool { + let video_queue = ash::khr::video_queue::Instance::load(entry, instance); + let profile = DecodeProfile::base(codec); + + let mut h264_capabilities = vk::VideoDecodeH264CapabilitiesKHR::default(); + let mut h265_capabilities = vk::VideoDecodeH265CapabilitiesKHR::default(); + let mut av1_capabilities = vk::VideoDecodeAV1CapabilitiesKHR::default(); + let mut decode_capabilities = vk::VideoDecodeCapabilitiesKHR::default(); + let capabilities = vk::VideoCapabilitiesKHR::default().push(&mut decode_capabilities); + let mut capabilities = match codec { + Codec::H264 => capabilities.push(&mut h264_capabilities), + Codec::H265 => capabilities.push(&mut h265_capabilities), + Codec::AV1 => capabilities.push(&mut av1_capabilities), + }; + + let result = unsafe { + (video_queue.fp().get_physical_device_video_capabilities_khr)( + physical_device, + profile.info(), + &mut capabilities, + ) + }; + + match result { + vk::Result::SUCCESS => { + debug!( + "{:?} decode supported: max {}x{}, {} DPB slots", + codec, + capabilities.max_coded_extent.width, + capabilities.max_coded_extent.height, + capabilities.max_dpb_slots + ); + true + } + vk::Result::ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR => { + debug!("{:?} decode not supported on this device", codec); + false + } + _ => { + warn!( + "Failed to query {:?} decode capabilities: {:?}", + codec, result + ); + false + } } - codecs } /// Inner struct holding the actual Vulkan resources. @@ -1049,25 +1127,13 @@ impl VideoContext { } // Check codec support for decoding. - let mut decode_codecs = Vec::new(); - if decode_queue.is_some() { - let available_extensions = - unsafe { instance.enumerate_device_extension_properties(physical_device) } - .unwrap_or_default(); - let has_extension = |name: &std::ffi::CStr| -> bool { - available_extensions.iter().any(|ext| { - let ext_name = - unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) }; - ext_name == name - }) - }; - - if has_extension(ash::khr::video_decode_h264::NAME) - && Self::check_h264_decode_support(&entry, &instance, physical_device) - { - decode_codecs.push(Codec::H264); - debug!("Device {} supports H.264 decode", device_name); - } + let decode_codecs = if decode_queue.is_some() { + query_decode_codecs(&entry, &instance, physical_device) + } else { + Vec::new() + }; + for codec in &decode_codecs { + debug!("Device {} supports {:?} decode", device_name, codec); } // Check if all required encode/decode codecs are supported. @@ -1594,63 +1660,6 @@ impl VideoContext { } } - /// Check if a codec is supported for encoding. - fn check_h264_decode_support( - entry: &ash::Entry, - instance: &ash::Instance, - physical_device: vk::PhysicalDevice, - ) -> bool { - let video_queue = ash::khr::video_queue::Instance::load(entry, instance); - - // H.264 decode profile: High profile, progressive, 8-bit 4:2:0. - let mut h264_profile = vk::VideoDecodeH264ProfileInfoKHR::default() - .std_profile_idc( - ash::vk::native::StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH, - ) - .picture_layout(vk::VideoDecodeH264PictureLayoutFlagsKHR::PROGRESSIVE); - - let profile_info = vk::VideoProfileInfoKHR::default() - .video_codec_operation(vk::VideoCodecOperationFlagsKHR::DECODE_H264) - .chroma_subsampling(vk::VideoChromaSubsamplingFlagsKHR::TYPE_420) - .luma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8) - .chroma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::TYPE_8) - .push(&mut h264_profile); - - let mut h264_capabilities = vk::VideoDecodeH264CapabilitiesKHR::default(); - let mut decode_capabilities = vk::VideoDecodeCapabilitiesKHR::default(); - let mut capabilities = vk::VideoCapabilitiesKHR::default() - .push(&mut h264_capabilities) - .push(&mut decode_capabilities); - - let result = unsafe { - (video_queue.fp().get_physical_device_video_capabilities_khr)( - physical_device, - &profile_info, - &mut capabilities, - ) - }; - - match result { - vk::Result::SUCCESS => { - debug!( - "H.264 decode supported: max {}x{}, {} DPB slots", - capabilities.max_coded_extent.width, - capabilities.max_coded_extent.height, - capabilities.max_dpb_slots - ); - true - } - vk::Result::ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR => { - debug!("H.264 decode not supported on this device"); - false - } - _ => { - warn!("Failed to query H.264 decode capabilities: {:?}", result); - false - } - } - } - /// Check if the selected device supports encoding the given codec. /// /// This only answers for 8-bit 4:2:0; see diff --git a/tests/adopted.rs b/tests/adopted.rs index 6f64b1b2..a9b063dd 100644 --- a/tests/adopted.rs +++ b/tests/adopted.rs @@ -144,7 +144,10 @@ fn decode_all( stream: &[u8], ) -> Result>, Box> { let mut readback = Readback::new(context)?; - let mut decoder = Decoder::new(context.clone(), DecodeConfig::h264().with_byte_stream())?; + let mut decoder = Decoder::new( + context.clone(), + DecodeConfig::new(Codec::H264).with_byte_stream(), + )?; let mut frames = Vec::new(); decode_stream(&mut decoder, stream, |frame| { let data = readback.read(&frame)?; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 62dbbc98..5d8e85ca 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -309,10 +309,13 @@ impl Readback { // Crop to the visible region on the way out: the image is coded-size, // which is padded up to the codec's macroblock granularity. + // An odd size rounds the chroma up: the last chroma sample covers + // the last luma column (and row) on its own. let visible_y_stride = frame.width as usize * bytes; - let visible_uv_stride = visible_y_stride; + let visible_uv_stride = frame.width.div_ceil(2) as usize * 2 * bytes; + let uv_rows = frame.height.div_ceil(2) as usize; let mut y = Vec::with_capacity(visible_y_stride * frame.height as usize); - let mut uv = Vec::with_capacity(visible_uv_stride * frame.height as usize / 2); + let mut uv = Vec::with_capacity(visible_uv_stride * uv_rows); unsafe { // Map the whole allocation: a sub-range map has to respect // nonCoherentAtomSize, and WHOLE_SIZE sidesteps that. @@ -326,7 +329,7 @@ impl Readback { visible_y_stride, )); } - for row in 0..frame.height as usize / 2 { + for row in 0..uv_rows { uv.extend_from_slice(std::slice::from_raw_parts( ptr.add(y_size + row * uv_stride), visible_uv_stride, diff --git a/tests/data/av1/aom_10bit.expected b/tests/data/av1/aom_10bit.expected new file mode 100644 index 00000000..c2079d2c --- /dev/null +++ b/tests/data/av1/aom_10bit.expected @@ -0,0 +1,35 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=99 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 tx_mode=2 reduced_tx_set=0 header_bits=97 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=101 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=146 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=105 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=147 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=111 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=2 loop_filter_level[1]=2 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=4 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=1 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=125 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=4 loop_filter_level[3]=18 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=4 refresh_frame_flags=16 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=120 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=12 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=126 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=18 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=5 ref_frame_idx[1]=2 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=4 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=120 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=4 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=12 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=18 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=1 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=6 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=1 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=12 loop_filter_level[3]=18 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 diff --git a/tests/data/av1/aom_10bit.obu b/tests/data/av1/aom_10bit.obu new file mode 100644 index 00000000..872c2599 Binary files /dev/null and b/tests/data/av1/aom_10bit.obu differ diff --git a/tests/data/av1/aom_altref.expected b/tests/data/av1/aom_altref.expected new file mode 100644 index 00000000..d3b8bca6 --- /dev/null +++ b/tests/data/av1/aom_altref.expected @@ -0,0 +1,36 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=99 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reduced_tx_set=0 header_bits=97 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=0 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=102 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=146 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=106 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=147 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=111 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=12 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=1 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=125 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=4 refresh_frame_flags=16 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=120 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=147 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=126 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=12 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=5 ref_frame_idx[1]=2 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=4 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=122 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=131 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=12 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=130 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=6 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=1 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 diff --git a/tests/data/av1/aom_altref.obu b/tests/data/av1/aom_altref.obu new file mode 100644 index 00000000..27cf4624 Binary files /dev/null and b/tests/data/av1/aom_altref.obu differ diff --git a/tests/data/av1/aom_annexb.obu b/tests/data/av1/aom_annexb.obu new file mode 100644 index 00000000..c403cd21 Binary files /dev/null and b/tests/data/av1/aom_annexb.obu differ diff --git a/tests/data/av1/aom_errres.expected b/tests/data/av1/aom_errres.expected new file mode 100644 index 00000000..5c8f6b6c --- /dev/null +++ b/tests/data/av1/aom_errres.expected @@ -0,0 +1,42 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=55 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=33 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=2 loop_filter_level[3]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reduced_tx_set=0 header_bits=106 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=3 show_frame=0 showable_frame=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=56 order_hint=14 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 frame_width_minus_1=319 frame_height_minus_1=239 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=56 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=6 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=323 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=57 frame_size_override_flag=0 order_hint=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=74 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=10 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=313 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=58 frame_size_override_flag=0 order_hint=3 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=83 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=2 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=313 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=59 frame_size_override_flag=0 order_hint=1 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=10 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=312 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=60 frame_size_override_flag=0 order_hint=2 refresh_frame_flags=16 ref_frame_idx[0]=3 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=2 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=61 frame_size_override_flag=0 order_hint=3 refresh_frame_flags=0 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=6 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=312 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=62 frame_size_override_flag=0 order_hint=5 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=87 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=6 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=302 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=63 frame_size_override_flag=0 order_hint=4 refresh_frame_flags=64 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=92 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=2 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=64 frame_size_override_flag=0 order_hint=5 refresh_frame_flags=0 ref_frame_idx[0]=6 ref_frame_idx[1]=2 ref_frame_idx[2]=4 ref_frame_idx[3]=3 ref_frame_idx[4]=5 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=93 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=6 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=300 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=65 frame_size_override_flag=0 order_hint=6 refresh_frame_flags=128 ref_frame_idx[0]=5 ref_frame_idx[1]=6 ref_frame_idx[2]=2 ref_frame_idx[3]=4 ref_frame_idx[4]=1 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=93 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=10 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=299 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=66 frame_size_override_flag=0 order_hint=7 refresh_frame_flags=0 ref_frame_idx[0]=7 ref_frame_idx[1]=5 ref_frame_idx[2]=6 ref_frame_idx[3]=2 ref_frame_idx[4]=1 ref_frame_idx[5]=4 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=94 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=10 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=312 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=67 frame_size_override_flag=0 order_hint=10 refresh_frame_flags=8 ref_frame_idx[0]=1 ref_frame_idx[1]=7 ref_frame_idx[2]=5 ref_frame_idx[3]=6 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=84 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=6 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=302 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=68 frame_size_override_flag=0 order_hint=8 refresh_frame_flags=16 ref_frame_idx[0]=1 ref_frame_idx[1]=7 ref_frame_idx[2]=5 ref_frame_idx[3]=6 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=95 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=6 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=69 frame_size_override_flag=0 order_hint=9 refresh_frame_flags=4 ref_frame_idx[0]=4 ref_frame_idx[1]=1 ref_frame_idx[2]=7 ref_frame_idx[3]=5 ref_frame_idx[4]=3 ref_frame_idx[5]=6 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=96 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=10 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=70 frame_size_override_flag=0 order_hint=10 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=1 ref_frame_idx[3]=7 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=97 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=10 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 reduced_tx_set=0 header_bits=300 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=71 frame_size_override_flag=0 order_hint=12 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=4 ref_frame_idx[3]=1 ref_frame_idx[4]=7 ref_frame_idx[5]=5 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=90 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=10 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=312 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=72 frame_size_override_flag=0 order_hint=11 refresh_frame_flags=32 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=4 ref_frame_idx[3]=1 ref_frame_idx[4]=6 ref_frame_idx[5]=7 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=98 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=6 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=73 frame_size_override_flag=0 order_hint=12 refresh_frame_flags=0 ref_frame_idx[0]=5 ref_frame_idx[1]=3 ref_frame_idx[2]=2 ref_frame_idx[3]=4 ref_frame_idx[4]=6 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=99 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=6 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=74 frame_size_override_flag=0 order_hint=13 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=5 ref_frame_idx[2]=3 ref_frame_idx[3]=2 ref_frame_idx[4]=4 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=99 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=6 loop_filter_level[3]=8 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 reduced_tx_set=0 header_bits=301 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 current_frame_id=75 frame_size_override_flag=0 order_hint=14 refresh_frame_flags=0 ref_frame_idx[0]=7 ref_frame_idx[1]=6 ref_frame_idx[2]=5 ref_frame_idx[3]=3 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=100 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=6 loop_filter_level[3]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=1 reference_select=0 reduced_tx_set=0 header_bits=300 +T obu=6 diff --git a/tests/data/av1/aom_errres.obu b/tests/data/av1/aom_errres.obu new file mode 100644 index 00000000..032219b5 Binary files /dev/null and b/tests/data/av1/aom_errres.obu differ diff --git a/tests/data/av1/aom_grain.expected b/tests/data/av1/aom_grain.expected new file mode 100644 index 00000000..8b709a0a --- /dev/null +++ b/tests/data/av1/aom_grain.expected @@ -0,0 +1,35 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=98 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reduced_tx_set=0 apply_grain=1 grain_seed=7391 num_y_points=9 num_cb_points=8 num_cr_points=8 header_bits=1179 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=97 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=15 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=10772 update_grain=1 num_y_points=13 num_cb_points=10 num_cr_points=10 header_bits=1357 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=106 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=14153 update_grain=1 num_y_points=10 num_cb_points=10 num_cr_points=10 header_bits=1310 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=111 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=17534 update_grain=1 num_y_points=12 num_cb_points=10 num_cr_points=10 header_bits=1330 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=16 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=20915 update_grain=1 num_y_points=8 num_cb_points=8 num_cr_points=9 header_bits=1214 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=4 loop_filter_level[3]=7 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=24296 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=9 header_bits=1274 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=1 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=125 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=15 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=27677 update_grain=1 num_y_points=6 num_cb_points=6 num_cr_points=9 header_bits=1150 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=31058 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=9 header_bits=1274 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=4 refresh_frame_flags=16 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=121 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=15 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=34439 update_grain=1 num_y_points=11 num_cb_points=10 num_cr_points=9 header_bits=1298 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=126 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=37820 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=9 header_bits=1262 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=5 ref_frame_idx[1]=2 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=41201 update_grain=1 num_y_points=10 num_cb_points=10 num_cr_points=10 header_bits=1294 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=4 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=121 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=10 loop_filter_level[1]=10 loop_filter_level[2]=8 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=44582 update_grain=1 num_y_points=12 num_cb_points=10 num_cr_points=10 header_bits=1330 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=6 loop_filter_level[1]=6 loop_filter_level[2]=12 loop_filter_level[3]=11 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=47963 update_grain=1 num_y_points=12 num_cb_points=10 num_cr_points=9 header_bits=1322 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=2 loop_filter_level[1]=2 loop_filter_level[2]=8 loop_filter_level[3]=15 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=51344 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=9 header_bits=1262 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=1 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=6 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=1 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=14 loop_filter_level[1]=14 loop_filter_level[2]=8 loop_filter_level[3]=19 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=54725 update_grain=1 num_y_points=12 num_cb_points=10 num_cr_points=10 header_bits=1337 +T obu=6 diff --git a/tests/data/av1/aom_grain.obu b/tests/data/av1/aom_grain.obu new file mode 100644 index 00000000..5ee02a5b Binary files /dev/null and b/tests/data/av1/aom_grain.obu differ diff --git a/tests/data/av1/aom_mono.expected b/tests/data/av1/aom_mono.expected new file mode 100644 index 00000000..6ef36b80 --- /dev/null +++ b/tests/data/av1/aom_mono.expected @@ -0,0 +1,28 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=5 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 tx_mode=2 reduced_tx_set=0 header_bits=73 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=40 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=114 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=53 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=114 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=59 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=113 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=65 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=119 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=3 ref_frame_idx[1]=4 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=61 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=119 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=5 ref_frame_idx[1]=3 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=61 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=119 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=4 refresh_frame_flags=128 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=58 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=114 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=1 refresh_frame_flags=16 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=61 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=113 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=4 ref_frame_idx[1]=2 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=60 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=113 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=7 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=7 ref_frame_idx[1]=3 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=60 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=113 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=5 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=60 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=113 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=1 header_bits=4 diff --git a/tests/data/av1/aom_mono.obu b/tests/data/av1/aom_mono.obu new file mode 100644 index 00000000..2d5c203c Binary files /dev/null and b/tests/data/av1/aom_mono.obu differ diff --git a/tests/data/av1/aom_resize.expected b/tests/data/av1/aom_resize.expected new file mode 100644 index 00000000..26adff2c --- /dev/null +++ b/tests/data/av1/aom_resize.expected @@ -0,0 +1,37 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=0 frame_width_minus_1=159 frame_height_minus_1=119 render_width_minus_1=319 render_height_minus_1=239 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=8 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reduced_tx_set=0 header_bits=125 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=0 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=14 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 frame_width_minus_1=212 frame_height_minus_1=159 render_width_minus_1=319 render_height_minus_1=239 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=54 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=3 lr_unit_shift=1 lr_uv_shift=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=206 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=7 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=43 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=144 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=67 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=12 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=5 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=79 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=12 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=138 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=4 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=66 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=12 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=147 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=6 primary_ref_frame=1 refresh_frame_flags=16 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=63 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=10 primary_ref_frame=2 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=62 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=150 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=8 primary_ref_frame=1 refresh_frame_flags=8 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=18 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=9 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=93 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=5 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=12 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=5 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=79 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=12 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=150 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=11 primary_ref_frame=1 refresh_frame_flags=16 ref_frame_idx[0]=5 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=95 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=13 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=4 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=96 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=135 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=1 order_hint=14 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=97 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=1 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=136 +T obu=6 diff --git a/tests/data/av1/aom_resize.obu b/tests/data/av1/aom_resize.obu new file mode 100644 index 00000000..20f25a3c Binary files /dev/null and b/tests/data/av1/aom_resize.obu differ diff --git a/tests/data/av1/aom_superres.expected b/tests/data/av1/aom_superres.expected new file mode 100644 index 00000000..9c7958cb --- /dev/null +++ b/tests/data/av1/aom_superres.expected @@ -0,0 +1,37 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 use_superres=1 coded_denom=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=30 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=1 loop_filter_level[1]=1 loop_filter_level[2]=1 loop_filter_level[3]=2 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=1 lr_type[1]=2 lr_type[2]=2 lr_unit_shift=1 lr_uv_shift=0 tx_mode=2 reduced_tx_set=0 header_bits=93 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=0 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=54 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=5 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=3 lr_type[1]=2 lr_type[2]=3 lr_unit_shift=1 lr_uv_shift=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=153 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=73 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=153 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=82 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=5 loop_filter_level[3]=6 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=155 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=2 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=140 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=92 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=2 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=88 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=153 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=94 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=22 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=2 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=140 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=1 refresh_frame_flags=16 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=5 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=95 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=2 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=2 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=3 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=83 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=12 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=143 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=1 refresh_frame_flags=8 ref_frame_idx[0]=2 ref_frame_idx[1]=4 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=7 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=97 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=13 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=6 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=98 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=9 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=151 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=5 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=5 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=89 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=1 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=153 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=1 refresh_frame_flags=16 ref_frame_idx[0]=5 ref_frame_idx[1]=7 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=100 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=9 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=139 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=4 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=7 ref_frame_idx[5]=3 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=101 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=9 loop_filter_level[1]=9 loop_filter_level[2]=13 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=142 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=7 ref_frame_idx[6]=1 use_superres=1 coded_denom=3 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=102 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=9 loop_filter_level[3]=16 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=3 lr_type[1]=0 lr_type[2]=0 lr_unit_shift=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 diff --git a/tests/data/av1/aom_superres.obu b/tests/data/av1/aom_superres.obu new file mode 100644 index 00000000..aee68e6b Binary files /dev/null and b/tests/data/av1/aom_superres.obu differ diff --git a/tests/data/av1/aom_tilegroups.expected b/tests/data/av1/aom_tilegroups.expected new file mode 100644 index 00000000..50057a66 --- /dev/null +++ b/tests/data/av1/aom_tilegroups.expected @@ -0,0 +1,36 @@ +F obu=3 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=0 tile_size_bytes_minus1=3 base_q_idx=33 using_qmatrix=1 qm_y=5 qm_u=5 segmentation_enabled=1 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=0 cdef_bits=2 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 cdef_y_pri_strength[2]=11 cdef_y_sec_strength[2]=0 cdef_y_pri_strength[3]=0 cdef_y_sec_strength[3]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=3 lr_unit_shift=1 lr_uv_shift=0 tx_mode=2 reduced_tx_set=0 header_bits=290 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=93 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=5 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=252 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=4 ref_frame_idx[0]=1 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=93 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=13 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=184 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=2 ref_frame_idx[1]=1 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=93 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=172 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=0 refresh_frame_flags=16 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=1 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=94 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=13 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=172 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=2 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=95 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=184 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=5 ref_frame_idx[1]=4 ref_frame_idx[2]=3 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=1 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=96 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=13 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=184 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=97 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=190 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=7 ref_frame_idx[1]=6 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=2 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=98 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=4 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=252 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=0 refresh_frame_flags=4 ref_frame_idx[0]=7 ref_frame_idx[1]=6 ref_frame_idx[2]=5 ref_frame_idx[3]=1 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=99 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=4 loop_filter_level[3]=13 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=190 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=1 ref_frame_idx[4]=5 ref_frame_idx[5]=4 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=100 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=4 loop_filter_level[1]=4 loop_filter_level[2]=8 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=178 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 +F obu=3 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=16 ref_frame_idx[0]=3 ref_frame_idx[1]=2 ref_frame_idx[2]=7 ref_frame_idx[3]=1 ref_frame_idx[4]=6 ref_frame_idx[5]=5 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=2 tile_size_bytes_minus1=3 base_q_idx=101 using_qmatrix=1 qm_y=6 qm_u=6 segmentation_enabled=1 segmentation_update_map=0 segmentation_update_data=0 delta_q_present=1 delta_q_res=2 delta_lf_present=1 delta_lf_res=1 delta_lf_multi=0 loop_filter_level[0]=8 loop_filter_level[1]=8 loop_filter_level[2]=8 loop_filter_level[3]=9 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 lr_type[0]=0 lr_type[1]=0 lr_type[2]=0 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=178 +T obu=4 tile_start_and_end_present_flag=1 tg_start=0 tg_end=1 +T obu=4 tile_start_and_end_present_flag=1 tg_start=2 tg_end=3 diff --git a/tests/data/av1/aom_tilegroups.obu b/tests/data/av1/aom_tilegroups.obu new file mode 100644 index 00000000..c51257f2 Binary files /dev/null and b/tests/data/av1/aom_tilegroups.obu differ diff --git a/tests/data/av1/aom_tiles.expected b/tests/data/av1/aom_tiles.expected new file mode 100644 index 00000000..3e69aac7 --- /dev/null +++ b/tests/data/av1/aom_tiles.expected @@ -0,0 +1,36 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=0 allow_intrabc=0 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=99 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reduced_tx_set=0 header_bits=103 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=0 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=7 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=101 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=152 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=7 refresh_frame_flags=4 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=106 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=12 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=153 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=4 refresh_frame_flags=8 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=111 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=4 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=7 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=4 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 tile_start_and_end_present_flag=0 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=1 refresh_frame_flags=64 ref_frame_idx[0]=3 ref_frame_idx[1]=5 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=0 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=125 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=0 refresh_frame_flags=128 ref_frame_idx[0]=6 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=4 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=4 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 tile_start_and_end_present_flag=0 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=4 refresh_frame_flags=16 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=120 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=4 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=1 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=7 ref_frame_idx[2]=6 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=126 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=12 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=5 ref_frame_idx[1]=2 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=6 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 tile_start_and_end_present_flag=0 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=4 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=64 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=2 ref_frame_idx[5]=7 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=120 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=2 delta_lf_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=141 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=1 refresh_frame_flags=128 ref_frame_idx[0]=4 ref_frame_idx[1]=3 ref_frame_idx[2]=5 ref_frame_idx[3]=0 ref_frame_idx[4]=6 ref_frame_idx[5]=2 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=11 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=149 +T obu=6 tile_start_and_end_present_flag=0 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=6 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=1 refresh_frame_flags=4 ref_frame_idx[0]=6 ref_frame_idx[1]=7 ref_frame_idx[2]=4 ref_frame_idx[3]=0 ref_frame_idx[4]=3 ref_frame_idx[5]=5 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=127 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=8 loop_filter_level[3]=14 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 tx_mode=2 reference_select=1 skip_mode_present=1 allow_warped_motion=1 reduced_tx_set=0 header_bits=137 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=0 tile_size_bytes_minus1=0 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=4 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=136 +T obu=6 tile_start_and_end_present_flag=0 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=1 force_integer_mv=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=6 refresh_frame_flags=32 ref_frame_idx[0]=2 ref_frame_idx[1]=6 ref_frame_idx[2]=7 ref_frame_idx[3]=1 ref_frame_idx[4]=4 ref_frame_idx[5]=3 ref_frame_idx[6]=0 allow_high_precision_mv=0 is_filter_switchable=0 interpolation_filter=0 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=1 tile_rows_log2=1 context_update_tile_id=3 tile_size_bytes_minus1=1 base_q_idx=128 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=11 loop_filter_level[1]=11 loop_filter_level[2]=8 loop_filter_level[3]=10 loop_filter_sharpness=0 loop_filter_delta_enabled=1 loop_filter_delta_update=0 cdef_damping_minus_3=2 cdef_bits=1 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=11 cdef_y_sec_strength[1]=2 tx_mode=2 reference_select=0 allow_warped_motion=1 reduced_tx_set=0 header_bits=148 +T obu=6 tile_start_and_end_present_flag=0 diff --git a/tests/data/av1/aom_tiles.obu b/tests/data/av1/aom_tiles.obu new file mode 100644 index 00000000..262ae847 Binary files /dev/null and b/tests/data/av1/aom_tiles.obu differ diff --git a/tests/data/av1/extract_trace.py b/tests/data/av1/extract_trace.py new file mode 100644 index 00000000..f5e647c7 --- /dev/null +++ b/tests/data/av1/extract_trace.py @@ -0,0 +1,129 @@ +"""Reduce ffmpeg trace_headers output for an AV1 stream to the per-frame +expectations the parser tests check (src/decoder/av1/stream_tests.rs). + +One line per frame header ("F ...") and one per tile group ("T ..."). + +Usage: + ffmpeg -i x.obu -c copy -bsf:v trace_headers -f null - 2> x.trace + python3 extract_trace.py x.trace > x.expected + +The fixtures were made from testsrc2 (320x240, 15 frames; aom_mono 176x144) +with ffmpeg n9.0, libaom and SVT-AV1 4.2: + aom_altref: ffmpeg -c:v libaom-av1 -cpu-used 8 -lag-in-frames 16 -auto-alt-ref 1 -f obu + aom_tiles: ffmpeg -c:v libaom-av1 -cpu-used 8 -tiles 2x2 -row-mt 0 -f obu + aom_10bit: ffmpeg -c:v libaom-av1 -cpu-used 8 -pix_fmt yuv420p10le -f obu + aom_grain: ffmpeg -c:v libaom-av1 -cpu-used 8 -denoise-noise-level 10 -f obu + svt_grain: ffmpeg -c:v libsvtav1 -preset 10 -svtav1-params film-grain=8 -f obu + aom_errres: aomenc --cpu-used=8 --limit=15 --obu --error-resilient=1 --sframe-dist=5 --sframe-mode=1 + aom_superres: aomenc --cpu-used=8 --limit=15 --obu --superres-mode=1 --superres-denominator=12 --superres-kf-denominator=10 + aom_resize: aomenc --cpu-used=8 --limit=15 --obu --resize-mode=1 --resize-denominator=12 --resize-kf-denominator=16 + aom_tilegroups: aomenc --cpu-used=8 --limit=12 --obu --tile-columns=1 --tile-rows=1 --num-tile-groups=2 + --sb-size=128 --enable-qm=1 --aq-mode=1 --delta-lf-mode=1 --deltaq-mode=3 + --lag-in-frames=0 --timing-info=model + aom_mono: aomenc --cpu-used=8 --limit=12 --obu --monochrome --enable-restoration=1 + aom_annexb: aomenc --cpu-used=8 --limit=3 --obu --annexb=1 +""" + + +import re +import sys + +KEYS = { + "show_existing_frame", "frame_to_show_map_idx", "frame_type", "show_frame", + "showable_frame", "error_resilient_mode", "disable_cdf_update", + "allow_screen_content_tools", "force_integer_mv", "current_frame_id", + "frame_size_override_flag", "order_hint", "primary_ref_frame", + "refresh_frame_flags", "use_superres", "coded_denom", "allow_intrabc", + "allow_high_precision_mv", "interpolation_filter", "is_filter_switchable", + "use_ref_frame_mvs", "disable_frame_end_update_cdf", "tile_cols_log2", + "tile_rows_log2", "context_update_tile_id", "tile_size_bytes_minus1", + "base_q_idx", "using_qmatrix", "qm_y", "qm_u", "qm_v", "segmentation_enabled", + "segmentation_update_map", "segmentation_temporal_update", + "segmentation_update_data", "delta_q_present", "delta_q_res", + "delta_lf_present", "delta_lf_res", "delta_lf_multi", "loop_filter_sharpness", + "loop_filter_delta_enabled", "loop_filter_delta_update", "cdef_bits", + "cdef_damping_minus_3", "tx_mode", "reference_select", "skip_mode_present", + "allow_warped_motion", "reduced_tx_set", "apply_grain", "grain_seed", + "update_grain", "film_grain_params_ref_idx", "num_y_points", "num_cb_points", + "num_cr_points", "frame_width_minus_1", "frame_height_minus_1", + "render_width_minus_1", "render_height_minus_1", "lr_unit_shift", + "lr_uv_shift", +} +INDEXED = {"ref_frame_idx", "loop_filter_level", "lr_type", + "cdef_y_pri_strength", "cdef_y_sec_strength"} + +LINE = re.compile(r"^\[trace_headers @ 0x[0-9a-f]+\] (.*)$") +ELEM = re.compile(r"^(\d+)\s+(\S+)\s+([01]+) = (-?\d+)$") + + +def main(path): + out = [] + section = None + obu_type = None + obu_size = None + frame = None + tg = None + + def flush(): + nonlocal frame, tg + if frame is not None: + start, end, kv = frame + kv.append(("header_bits", end - start)) + out.append("F obu=%d " % frame_obu + " ".join("%s=%s" % p for p in kv)) + frame = None + if tg is not None: + out.append("T obu=%d " % tg_obu + " ".join("%s=%s" % p for p in tg)) + tg = None + + frame_obu = tg_obu = 0 + for raw in open(path, errors="replace"): + m = LINE.match(raw.rstrip("\n")) + if not m: + continue + body = m.group(1).strip() + e = ELEM.match(body) + if not e: + if body in ("Frame Header", "Tile Group", "Redundant Frame Header"): + if body == "Tile Group": + flush_frame_only = frame is not None + if flush_frame_only: + start, end, kv = frame + kv.append(("header_bits", end - start)) + out.append("F obu=%d " % frame_obu + " ".join("%s=%s" % p for p in kv)) + frame = None + tg = [] + tg_obu = obu_type + elif body == "Frame Header": + frame = [None, None, []] + frame_obu = obu_type + section = body + elif body == "OBU header": + flush() + section = "OBU header" + else: + section = body + continue + pos, name, bits, value = int(e.group(1)), e.group(2), e.group(3), e.group(4) + if section == "OBU header": + if name == "obu_type": + obu_type = int(value) + if name == "obu_size": + obu_size = int(value) + continue + if section == "Frame Header" and frame is not None: + if name in ("zero_bit", "trailing_one_bit", "trailing_zero_bit"): + continue + if frame[0] is None: + frame[0] = pos + frame[1] = pos + len(bits) + base = name.split("[")[0] + if name in KEYS or base in INDEXED: + frame[2].append((name, value)) + elif section == "Tile Group" and tg is not None: + if name in ("tile_start_and_end_present_flag", "tg_start", "tg_end"): + tg.append((name, value)) + flush() + print("\n".join(out)) + + +main(sys.argv[1]) diff --git a/tests/data/av1/svt_grain.expected b/tests/data/av1/svt_grain.expected new file mode 100644 index 00000000..02d96b63 --- /dev/null +++ b/tests/data/av1/svt_grain.expected @@ -0,0 +1,37 @@ +F obu=6 show_existing_frame=0 frame_type=0 show_frame=1 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=0 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=69 using_qmatrix=0 segmentation_enabled=0 delta_q_present=1 delta_q_res=0 delta_lf_present=0 loop_filter_level[0]=3 loop_filter_level[1]=3 loop_filter_level[2]=1 loop_filter_level[3]=1 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=2 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=0 cdef_y_pri_strength[2]=0 cdef_y_sec_strength[2]=0 cdef_y_pri_strength[3]=15 cdef_y_sec_strength[3]=0 tx_mode=1 reduced_tx_set=0 apply_grain=1 grain_seed=7391 num_y_points=8 num_cb_points=10 num_cr_points=10 header_bits=1248 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=8 primary_ref_frame=0 refresh_frame_flags=2 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=91 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=2 loop_filter_level[3]=2 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=1 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 cdef_y_pri_strength[1]=0 cdef_y_sec_strength[1]=2 tx_mode=1 reference_select=1 allow_warped_motion=1 reduced_tx_set=0 apply_grain=1 grain_seed=34439 update_grain=1 num_y_points=10 num_cb_points=8 num_cr_points=10 header_bits=1270 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=4 primary_ref_frame=0 refresh_frame_flags=16 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=1 ref_frame_idx[5]=1 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=111 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=20915 update_grain=1 num_y_points=8 num_cb_points=9 num_cr_points=10 header_bits=1231 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=2 primary_ref_frame=4 refresh_frame_flags=32 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=4 ref_frame_idx[5]=4 ref_frame_idx[6]=4 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=124 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=14153 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=10 header_bits=1263 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=1 primary_ref_frame=4 refresh_frame_flags=0 ref_frame_idx[0]=0 ref_frame_idx[1]=0 ref_frame_idx[2]=0 ref_frame_idx[3]=0 ref_frame_idx[4]=5 ref_frame_idx[5]=5 ref_frame_idx[6]=5 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=7 cdef_y_sec_strength[0]=3 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=10772 update_grain=1 num_y_points=11 num_cb_points=10 num_cr_points=10 header_bits=1294 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=5 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=3 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=5 ref_frame_idx[1]=5 ref_frame_idx[2]=5 ref_frame_idx[3]=5 ref_frame_idx[4]=4 ref_frame_idx[5]=4 ref_frame_idx[6]=4 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=17534 update_grain=1 num_y_points=7 num_cb_points=10 num_cr_points=10 header_bits=1230 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=4 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=6 primary_ref_frame=0 refresh_frame_flags=32 ref_frame_idx[0]=4 ref_frame_idx[1]=4 ref_frame_idx[2]=4 ref_frame_idx[3]=4 ref_frame_idx[4]=1 ref_frame_idx[5]=1 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=124 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=27677 update_grain=1 num_y_points=10 num_cb_points=10 num_cr_points=10 header_bits=1279 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=5 primary_ref_frame=4 refresh_frame_flags=0 ref_frame_idx[0]=4 ref_frame_idx[1]=4 ref_frame_idx[2]=4 ref_frame_idx[3]=4 ref_frame_idx[4]=5 ref_frame_idx[5]=5 ref_frame_idx[6]=5 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=0 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=24296 update_grain=1 num_y_points=11 num_cb_points=9 num_cr_points=10 header_bits=1278 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=5 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=7 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=5 ref_frame_idx[1]=5 ref_frame_idx[2]=5 ref_frame_idx[3]=5 ref_frame_idx[4]=1 ref_frame_idx[5]=1 ref_frame_idx[6]=1 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=8 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=31058 update_grain=1 num_y_points=10 num_cb_points=10 num_cr_points=10 header_bits=1278 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=1 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=12 primary_ref_frame=0 refresh_frame_flags=4 ref_frame_idx[0]=1 ref_frame_idx[1]=1 ref_frame_idx[2]=1 ref_frame_idx[3]=1 ref_frame_idx[4]=1 ref_frame_idx[5]=1 ref_frame_idx[6]=1 allow_high_precision_mv=1 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=109 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=5 loop_filter_level[1]=5 loop_filter_level[2]=2 loop_filter_level[3]=2 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=1 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=47963 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=10 header_bits=1274 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=10 primary_ref_frame=0 refresh_frame_flags=8 ref_frame_idx[0]=1 ref_frame_idx[1]=1 ref_frame_idx[2]=1 ref_frame_idx[3]=1 ref_frame_idx[4]=2 ref_frame_idx[5]=2 ref_frame_idx[6]=2 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=132 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=41201 update_grain=1 num_y_points=7 num_cb_points=8 num_cr_points=9 header_bits=1183 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=9 primary_ref_frame=4 refresh_frame_flags=0 ref_frame_idx[0]=1 ref_frame_idx[1]=1 ref_frame_idx[2]=1 ref_frame_idx[3]=1 ref_frame_idx[4]=3 ref_frame_idx[5]=3 ref_frame_idx[6]=3 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=8 cdef_y_sec_strength[0]=0 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=37820 update_grain=1 num_y_points=7 num_cb_points=10 num_cr_points=10 header_bits=1230 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=3 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=11 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=3 ref_frame_idx[1]=3 ref_frame_idx[2]=3 ref_frame_idx[3]=3 ref_frame_idx[4]=2 ref_frame_idx[5]=2 ref_frame_idx[6]=2 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=44582 update_grain=1 num_y_points=10 num_cb_points=10 num_cr_points=10 header_bits=1278 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=2 header_bits=4 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=0 showable_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=14 primary_ref_frame=0 refresh_frame_flags=1 ref_frame_idx[0]=2 ref_frame_idx[1]=2 ref_frame_idx[2]=2 ref_frame_idx[3]=2 ref_frame_idx[4]=2 ref_frame_idx[5]=2 ref_frame_idx[6]=2 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=137 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=7 loop_filter_level[1]=7 loop_filter_level[2]=3 loop_filter_level[3]=3 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=54725 update_grain=1 num_y_points=10 num_cb_points=9 num_cr_points=10 header_bits=1274 +T obu=6 +F obu=6 show_existing_frame=0 frame_type=1 show_frame=1 error_resilient_mode=0 disable_cdf_update=0 allow_screen_content_tools=0 frame_size_override_flag=0 order_hint=13 primary_ref_frame=0 refresh_frame_flags=0 ref_frame_idx[0]=2 ref_frame_idx[1]=2 ref_frame_idx[2]=2 ref_frame_idx[3]=2 ref_frame_idx[4]=0 ref_frame_idx[5]=0 ref_frame_idx[6]=0 allow_high_precision_mv=0 is_filter_switchable=1 use_ref_frame_mvs=1 disable_frame_end_update_cdf=0 tile_cols_log2=0 tile_rows_log2=0 base_q_idx=140 using_qmatrix=0 segmentation_enabled=0 delta_q_present=0 loop_filter_level[0]=0 loop_filter_level[1]=0 loop_filter_sharpness=0 loop_filter_delta_enabled=0 cdef_damping_minus_3=2 cdef_bits=0 cdef_y_pri_strength[0]=15 cdef_y_sec_strength[0]=2 tx_mode=1 reference_select=1 skip_mode_present=1 allow_warped_motion=0 reduced_tx_set=0 apply_grain=1 grain_seed=51344 update_grain=1 num_y_points=9 num_cb_points=10 num_cr_points=10 header_bits=1262 +T obu=6 +F obu=3 show_existing_frame=1 frame_to_show_map_idx=0 header_bits=4 diff --git a/tests/data/av1/svt_grain.obu b/tests/data/av1/svt_grain.obu new file mode 100644 index 00000000..8bce74be Binary files /dev/null and b/tests/data/av1/svt_grain.obu differ diff --git a/tests/data/closedgop.265 b/tests/data/closedgop.265 new file mode 100644 index 00000000..53e57a8b Binary files /dev/null and b/tests/data/closedgop.265 differ diff --git a/tests/data/cra_start.265 b/tests/data/cra_start.265 new file mode 100644 index 00000000..61565eee Binary files /dev/null and b/tests/data/cra_start.265 differ diff --git a/tests/data/main10.265 b/tests/data/main10.265 new file mode 100644 index 00000000..0f7d2953 Binary files /dev/null and b/tests/data/main10.265 differ diff --git a/tests/data/nvenc.265 b/tests/data/nvenc.265 new file mode 100644 index 00000000..8cf900b0 Binary files /dev/null and b/tests/data/nvenc.265 differ diff --git a/tests/data/opengop.265 b/tests/data/opengop.265 new file mode 100644 index 00000000..4b8e77bd Binary files /dev/null and b/tests/data/opengop.265 differ diff --git a/tests/data/scaling.265 b/tests/data/scaling.265 new file mode 100644 index 00000000..d464999f Binary files /dev/null and b/tests/data/scaling.265 differ diff --git a/tests/data/slices.265 b/tests/data/slices.265 new file mode 100644 index 00000000..e51a21f6 Binary files /dev/null and b/tests/data/slices.265 differ diff --git a/tests/data/temporal.265 b/tests/data/temporal.265 new file mode 100644 index 00000000..baada1f5 Binary files /dev/null and b/tests/data/temporal.265 differ diff --git a/tests/decode_streams.rs b/tests/decode_streams.rs new file mode 100644 index 00000000..e51feefb --- /dev/null +++ b/tests/decode_streams.rs @@ -0,0 +1,104 @@ +//! Decode third-party streams and compare against ffmpeg's software decoder, +//! which must match byte for byte. +//! +//! The streams are the fixtures in `tests/data`; how each was made is in the +//! parser tests that use them. Each is fed as a byte stream that the decoder +//! frames itself, and takes whichever output path the device offers. +//! +//! `base.264` is left out: on NVIDIA (615.78.08) it differs from ffmpeg from +//! frame 17, the first after its `frame_num` wraps. +//! +//! Ignored by default: requires a Vulkan Video device and ffmpeg. Run with +//! `cargo test -- --ignored`. + +use std::fs::File; +use std::path::Path; + +#[allow(dead_code)] +mod common; +use common::{Readback, decode_stream, write_nv12}; + +use pixelforge::decoder::{DecodeConfig, Decoder}; +use pixelforge::{Codec, VideoContextBuilder}; + +/// Decode `name` from `tests/data` and compare it against ffmpeg. +fn check(codec: Codec, name: &str, pix_fmt: &str) -> Result<(), Box> { + common::init_logging(); + let input = Path::new("tests/data").join(name); + let dir = common::scratch_dir("decode_streams"); + let file = name.replace('/', "_"); + let output_path = dir.join(format!("{file}.yuv")); + let reference_path = dir.join(format!("{file}.ref.yuv")); + + // dav1d is the AV1 reference. `-noautoscale` keeps each frame at its own + // size, where ffmpeg would otherwise scale a stream that changes size to + // its first one. + let mut ffmpeg = std::process::Command::new("ffmpeg"); + ffmpeg.args(["-hide_banner", "-loglevel", "error", "-y"]); + if codec == Codec::AV1 { + ffmpeg.args(["-c:v", "libdav1d"]); + } + let status = ffmpeg + .arg("-i") + .arg(&input) + .args(["-noautoscale", "-pix_fmt", pix_fmt, "-f", "rawvideo"]) + .arg(&reference_path) + .status()?; + assert!(status.success(), "ffmpeg failed to decode {name}"); + + let context = VideoContextBuilder::new() + .app_name("pixelforge-decode-streams") + .require_decode(codec) + .enable_validation(std::env::var("PIXELFORGE_VALIDATION").is_ok()) + .build()?; + let mut readback = Some(Readback::new(&context)?); + let mut output = Some(File::create(&output_path)?); + let mut decoder = Decoder::new(context, DecodeConfig::new(codec).with_byte_stream())?; + let stream = std::fs::read(&input)?; + decode_stream(&mut decoder, &stream, |frame| { + write_nv12(&frame, &mut readback, &mut output) + })?; + drop(output); + + assert!( + std::fs::read(&reference_path)? == std::fs::read(&output_path)?, + "{name}: decoded frames differ from ffmpeg (see {})", + output_path.display() + ); + Ok(()) +} + +macro_rules! stream_tests { + ($($test:ident: $codec:expr, $name:literal, $pix_fmt:literal;)*) => { + $( + #[test] + #[ignore = "requires a Vulkan Video device and ffmpeg"] + fn $test() -> Result<(), Box> { + check($codec, $name, $pix_fmt) + } + )* + }; +} + +stream_tests! { + h264_bframes: Codec::H264, "bframes.264", "nv12"; + h264_multislice: Codec::H264, "multislice.264", "nv12"; + h264_zerolatency: Codec::H264, "zerolatency.264", "nv12"; + h265_closedgop: Codec::H265, "closedgop.265", "nv12"; + h265_cra_start: Codec::H265, "cra_start.265", "nv12"; + h265_main10: Codec::H265, "main10.265", "p010le"; + h265_nvenc: Codec::H265, "nvenc.265", "nv12"; + h265_opengop: Codec::H265, "opengop.265", "nv12"; + h265_scaling: Codec::H265, "scaling.265", "nv12"; + h265_slices: Codec::H265, "slices.265", "nv12"; + h265_temporal: Codec::H265, "temporal.265", "nv12"; + av1_altref: Codec::AV1, "av1/aom_altref.obu", "nv12"; + av1_tiles: Codec::AV1, "av1/aom_tiles.obu", "nv12"; + av1_tilegroups: Codec::AV1, "av1/aom_tilegroups.obu", "nv12"; + av1_errres: Codec::AV1, "av1/aom_errres.obu", "nv12"; + av1_superres: Codec::AV1, "av1/aom_superres.obu", "nv12"; + av1_resize: Codec::AV1, "av1/aom_resize.obu", "nv12"; + av1_10bit: Codec::AV1, "av1/aom_10bit.obu", "p010le"; + av1_grain: Codec::AV1, "av1/aom_grain.obu", "nv12"; + av1_svt_grain: Codec::AV1, "av1/svt_grain.obu", "nv12"; +} diff --git a/tests/dmabuf.rs b/tests/dmabuf.rs index 4fa7d352..9f3a236f 100644 --- a/tests/dmabuf.rs +++ b/tests/dmabuf.rs @@ -606,7 +606,10 @@ fn unaligned_sizes_are_read_at_the_display_size() -> Result<(), Box Result>, Box> { let mut readback = Readback::new(context)?; - let mut decoder = Decoder::new(context.clone(), DecodeConfig::h264().with_byte_stream())?; + let mut decoder = Decoder::new( + context.clone(), + DecodeConfig::new(Codec::H264).with_byte_stream(), + )?; let mut frames = Vec::new(); decode_stream(&mut decoder, stream, |frame| { frames.push(readback.read(&frame)?.y); diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index ec51583a..2fa1cb3c 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -6,8 +6,9 @@ //! It is also where B-frames will be exercised once the encoder supports them. //! //! The input is planar YUV420 (I420). The decoded output is NV12 in display -//! order. The assertion here is that no frame is lost across the round trip; -//! the decoded file is left in the scratch dir for inspection. +//! order. The assertions are that no frame is lost across the round trip, and +//! that the output is byte-identical to ffmpeg's software decode of the same +//! stream. The files are left in the scratch dir for inspection. //! //! Ignored by default: requires a Vulkan Video device and ffmpeg. Run with //! `cargo test -- --ignored`. @@ -32,18 +33,34 @@ const GOP: u32 = 30; #[test] #[ignore = "requires a Vulkan Video device and ffmpeg"] fn roundtrip_h264() -> Result<(), Box> { - tracing_subscriber::fmt::init(); + roundtrip(Codec::H264, "h264") +} + +#[test] +#[ignore = "requires a Vulkan Video device and ffmpeg"] +fn roundtrip_h265() -> Result<(), Box> { + roundtrip(Codec::H265, "h265") +} + +#[test] +#[ignore = "requires a Vulkan Video device and ffmpeg"] +fn roundtrip_av1() -> Result<(), Box> { + roundtrip(Codec::AV1, "obu") +} + +fn roundtrip(codec: Codec, extension: &str) -> Result<(), Box> { + let _ = tracing_subscriber::fmt::try_init(); let input_path = format!("testdata/test_frames_{WIDTH}x{HEIGHT}_yuv420p.yuv"); common::ensure_test_data(WIDTH, HEIGHT, "yuv420p", &input_path)?; - let dir = common::scratch_dir("roundtrip"); - let bitstream_path = dir.join("out.h264"); + let dir = common::scratch_dir(&format!("roundtrip-{extension}")); + let bitstream_path = dir.join(format!("out.{extension}")); let context = VideoContextBuilder::new() .app_name("pixelforge-roundtrip") - .require_encode(Codec::H264) - .require_decode(Codec::H264) + .require_encode(codec) + .require_decode(codec) .enable_validation(std::env::var("PIXELFORGE_VALIDATION").is_ok()) .build()?; @@ -53,7 +70,7 @@ fn roundtrip_h264() -> Result<(), Box> { let frame_size = (WIDTH * HEIGHT * 3 / 2) as usize; let frame_count = yuv.len() / frame_size; - let config = EncodeConfig::h264(WIDTH, HEIGHT) + let config = EncodeConfig::new(codec, WIDTH, HEIGHT) .with_rate_control(RateControl::Cqp { qp: 26 }) .with_frame_rate(30, 1) .with_gop_size(GOP); @@ -89,7 +106,7 @@ fn roundtrip_h264() -> Result<(), Box> { let yuv_path = dir.join("out.yuv"); let mut out = Some(File::create(&yuv_path)?); let mut readback = Some(Readback::new(&context)?); - let mut decoder = Decoder::new(context, DecodeConfig::h264().with_byte_stream())?; + let mut decoder = Decoder::new(context, DecodeConfig::new(codec).with_byte_stream())?; let mut decoded_count = 0usize; decode_stream(&mut decoder, &bitstream, |frame| { @@ -104,6 +121,27 @@ fn roundtrip_h264() -> Result<(), Box> { "round trip lost frames: encoded {frame_count} but decoded {decoded_count}" ); println!("Wrote decoded frames to {}", yuv_path.display()); + drop(out); + + // --- Compare against ffmpeg's software decoder --- + let reference_path = dir.join("reference.yuv"); + let mut ffmpeg = std::process::Command::new("ffmpeg"); + ffmpeg.args(["-hide_banner", "-loglevel", "error", "-y"]); + if codec == Codec::AV1 { + ffmpeg.args(["-c:v", "libdav1d"]); + } + let status = ffmpeg + .arg("-i") + .arg(&bitstream_path) + .args(["-pix_fmt", "nv12", "-f", "rawvideo"]) + .arg(&reference_path) + .status()?; + assert!(status.success(), "ffmpeg failed to decode the stream"); + assert!( + std::fs::read(&reference_path)? == std::fs::read(&yuv_path)?, + "decoded frames differ from ffmpeg's decode of {}", + bitstream_path.display() + ); Ok(()) } diff --git a/tests/submit.rs b/tests/submit.rs index 0612812c..c83e8bf7 100644 --- a/tests/submit.rs +++ b/tests/submit.rs @@ -131,7 +131,7 @@ fn repeat_encodes_the_last_frame_again() -> Result<(), Box