diff --git a/Cargo.lock b/Cargo.lock
index 487469a7..9f719168 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
dependencies = [
"android_log-sys",
- "env_filter",
+ "env_filter 0.1.4",
"log",
]
@@ -446,7 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"annotate-snippets",
- "bitflags",
+ "bitflags 2.13.0",
"cexpr",
"clang-sys",
"itertools 0.13.0",
@@ -475,6 +475,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.13.0"
@@ -556,7 +562,7 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"cairo-sys-rs",
"glib",
"libc",
@@ -885,6 +891,15 @@ dependencies = [
"itertools 0.10.5",
]
+[[package]]
+name = "cros-codecs"
+version = "0.0.5"
+dependencies = [
+ "env_logger",
+ "log",
+ "serde_json",
+]
+
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@@ -991,6 +1006,37 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror 2.0.18",
+]
+
[[package]]
name = "der"
version = "0.7.10"
@@ -1101,6 +1147,29 @@ dependencies = [
"regex",
]
+[[package]]
+name = "env_filter"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter 2.0.0",
+ "jiff",
+ "log",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -1193,7 +1262,7 @@ version = "8.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"ffmpeg-sys-next",
"libc",
]
@@ -1584,7 +1653,7 @@ version = "0.22.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"futures-channel",
"futures-core",
"futures-executor",
@@ -2127,6 +2196,42 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+[[package]]
+name = "jiff"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
+dependencies = [
+ "defmt",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "jni"
version = "0.21.1"
@@ -2291,7 +2396,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"cc",
"convert_case",
"cookie-factory",
@@ -2493,7 +2598,7 @@ dependencies = [
name = "ndk"
version = "0.9.0"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"jni-sys 0.3.1",
"log",
"ndk-sys",
@@ -2517,7 +2622,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2530,7 +2635,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2848,6 +2953,14 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+[[package]]
+name = "pf-bitstream"
+version = "0.24.0"
+dependencies = [
+ "cros-codecs",
+ "tracing",
+]
+
[[package]]
name = "pf-capture"
version = "0.24.0"
@@ -3075,7 +3188,7 @@ version = "0.24.0"
dependencies = [
"anyhow",
"ashpd",
- "bitflags",
+ "bitflags 2.13.0",
"bytemuck",
"futures-util",
"hex",
@@ -3153,7 +3266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44"
dependencies = [
"anyhow",
- "bitflags",
+ "bitflags 2.13.0",
"libc",
"libspa",
"libspa-sys",
@@ -3207,7 +3320,7 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"crc32fast",
"fdeflate",
"flate2",
@@ -3251,6 +3364,21 @@ dependencies = [
"universal-hash",
]
+[[package]]
+name = "portable-atomic"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+dependencies = [
+ "portable-atomic",
+]
+
[[package]]
name = "potential_utf"
version = "0.1.5"
@@ -3311,7 +3439,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set",
"bit-vec",
- "bitflags",
+ "bitflags 2.13.0",
"num-traits",
"rand 0.9.4",
"rand_chacha 0.9.0",
@@ -3777,7 +3905,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
]
[[package]]
@@ -3934,7 +4062,7 @@ version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -3973,7 +4101,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"errno",
"libc",
"linux-raw-sys",
@@ -4127,7 +4255,7 @@ version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bd22eb1bbc9137e914022b4994ed35591eea0884e9e3e98e6d9895cad6e1d2"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"libc",
"sdl3-image-sys",
"sdl3-mixer-sys",
@@ -4222,7 +4350,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -4428,7 +4556,7 @@ version = "0.87.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f7d94f3e7537c71ad4cf132eb26e3be8c8a886ed3649c4525c089041fc312b2"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"lazy_static",
"skia-bindings",
]
@@ -5301,7 +5429,7 @@ version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"rustix",
"wayland-backend",
"wayland-scanner",
@@ -5313,7 +5441,7 @@ version = "0.32.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"wayland-backend",
"wayland-client",
"wayland-scanner",
@@ -5325,7 +5453,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"wayland-backend",
"wayland-client",
"wayland-protocols",
@@ -5338,7 +5466,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"wayland-backend",
"wayland-client",
"wayland-protocols",
@@ -5690,7 +5818,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a"
dependencies = [
- "bitflags",
+ "bitflags 2.13.0",
"widestring",
"windows-sys 0.52.0",
]
diff --git a/Cargo.toml b/Cargo.toml
index 8ee7bd85..003b9b97 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,8 @@ members = [
"crates/punktfunk-host",
"crates/punktfunk-host/vendor/usbip-sim",
"crates/punktfunk-tray",
+ "crates/pf-bitstream",
+ "crates/pf-bitstream/vendor/cros-codecs",
"crates/pf-client-core",
"crates/pf-clipboard",
"crates/pf-presenter",
diff --git a/crates/pf-bitstream/Cargo.toml b/crates/pf-bitstream/Cargo.toml
new file mode 100644
index 00000000..fd565e38
--- /dev/null
+++ b/crates/pf-bitstream/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "pf-bitstream"
+description = "Client-side bitstream layer for native decode: AU parsing, POC/DPB/reference derivation and per-AU DecodePlans (H.264/HEVC/AV1) on the vendored cros-codecs parsers — the layer libavcodec used to be (design/client-native-decode.md §3.1)"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+authors.workspace = true
+repository.workspace = true
+
+[dependencies]
+cros-codecs = { path = "vendor/cros-codecs" }
+tracing = "0.1"
+
+[lints]
+workspace = true
diff --git a/crates/pf-bitstream/src/lib.rs b/crates/pf-bitstream/src/lib.rs
new file mode 100644
index 00000000..ed7fa7b6
--- /dev/null
+++ b/crates/pf-bitstream/src/lib.rs
@@ -0,0 +1,149 @@
+//! The client's bitstream layer for native decode (design/client-native-decode.md §3.1):
+//! everything a stateless hardware decoder needs to know about an AU before submission —
+//! parsed headers, POC, DPB state, reference lists (including MMCO/LTR, which the hosts'
+//! RFI recovery actively uses), recovery-point SEI — derived once here and consumed by
+//! every backend (Vulkan `StdVideo*`, DXVA picparams, libva buffers).
+//!
+//! Parsing primitives come from the vendored cros-codecs parser layer
+//! (`vendor/cros-codecs`, see its PROVENANCE.md); this crate owns what upstream keeps in
+//! its Linux-only `decoder::stateless` half — the per-AU orchestration — plus the pieces
+//! upstream lacks (SEI payload parsing: their parsers classify SEI NALUs but never read
+//! them).
+//!
+//! Scope discipline: punktfunk clients decode punktfunk hosts — zero-reorder, no
+//! B-frames, progressive, parameter sets from encoders we control. Implement to spec
+//! where cheap; reject-with-log outside that envelope rather than half-decode.
+//!
+//! Nothing in this crate may touch a GPU API, an OS handle, or the network: CPU-only by
+//! construction, so its tests run on every CI leg including macOS.
+
+// The vendor-pinning smoke tests below assert against byte counts and golden values from
+// the vendored snapshot's own test vectors; a cros-codecs re-sync that shifts parser
+// behavior must trip HERE, in our tree, not in a decode session.
+#[cfg(test)]
+mod vendor_smoke {
+ use std::io::Cursor;
+
+ use cros_codecs::bitstream_utils::IvfIterator;
+ use cros_codecs::codec::av1::parser::ObuAction;
+ use cros_codecs::codec::av1::parser::ParsedObu;
+ use cros_codecs::codec::h264::parser::Nalu as H264Nalu;
+ use cros_codecs::codec::h264::parser::Parser as H264Parser;
+ use cros_codecs::codec::h265::parser::Nalu as H265Nalu;
+ use cros_codecs::codec::h265::parser::Parser as H265Parser;
+
+ const H264_25FPS: &[u8] =
+ include_bytes!("../vendor/cros-codecs/src/codec/h264/test_data/test-25fps.h264");
+ const H265_25FPS: &[u8] =
+ include_bytes!("../vendor/cros-codecs/src/codec/h265/test_data/test-25fps.h265");
+ const AV1_25FPS: &[u8] =
+ include_bytes!("../vendor/cros-codecs/src/codec/av1/test_data/test-25fps.ivf.av1");
+ const VP9_25FPS: &[u8] =
+ include_bytes!("../vendor/cros-codecs/src/codec/vp9/test_data/test-25fps.vp9");
+
+ #[test]
+ fn h264_parses_the_vendored_vector_to_its_goldens() {
+ let mut cursor = Cursor::new(H264_25FPS);
+ let mut parser = H264Parser::default();
+ let (mut nalus, mut sps, mut slices) = (0u32, 0u32, 0u32);
+ let mut coded = (0u32, 0u32);
+ while let Ok(nalu) = H264Nalu::next(&mut cursor) {
+ nalus += 1;
+ if let Ok(s) = parser.parse_sps(&nalu) {
+ sps += 1;
+ coded = (
+ (s.pic_width_in_mbs_minus1 as u32 + 1) * 16,
+ (s.pic_height_in_map_units_minus1 as u32 + 1) * 16,
+ );
+ continue;
+ }
+ if parser.parse_pps(&nalu).is_ok() {
+ continue;
+ }
+ if parser.parse_slice_header(nalu).is_ok() {
+ slices += 1;
+ }
+ }
+ // 759 is upstream's own golden for this stream (chromium h264_parser_unittest lineage).
+ assert_eq!(nalus, 759);
+ assert_eq!(sps, 4);
+ assert_eq!(slices, 500);
+ assert_eq!(coded, (320, 240));
+ }
+
+ #[test]
+ fn h265_parses_the_vendored_vector() {
+ let mut cursor = Cursor::new(H265_25FPS);
+ let mut parser = H265Parser::default();
+ let (mut nalus, mut sps, mut slices) = (0u32, 0u32, 0u32);
+ while let Ok(nalu) = H265Nalu::next(&mut cursor) {
+ nalus += 1;
+ if parser.parse_sps(&nalu).is_ok() {
+ sps += 1;
+ continue;
+ }
+ if parser.parse_pps(&nalu).is_ok() {
+ continue;
+ }
+ if parser.parse_slice_header(nalu).is_ok() {
+ slices += 1;
+ }
+ }
+ assert_eq!(nalus, 254);
+ assert_eq!(sps, 1);
+ assert_eq!(slices, 250);
+ }
+
+ #[test]
+ fn av1_walks_obus_and_maintains_ref_slots_across_the_stream() {
+ let mut parser = cros_codecs::codec::av1::parser::Parser::default();
+ let (mut obus, mut frames) = (0u32, 0u32);
+ for packet in IvfIterator::new(AV1_25FPS) {
+ let mut consumed = 0;
+ while let Ok(action) = parser.read_obu(&packet[consumed..]) {
+ let obu = match action {
+ ObuAction::Process(obu) => obu,
+ ObuAction::Drop(n) => {
+ consumed += n as usize;
+ continue;
+ }
+ };
+ consumed += obu.bytes_used;
+ obus += 1;
+ // `ref_frame_update` is the parser's ref-slot bookkeeping; without it,
+ // inter frames fail with "Reference is invalid" — the parser validates
+ // reference integrity rather than trusting the stream.
+ match parser.parse_obu(obu).expect("parse_obu") {
+ ParsedObu::FrameHeader(fh) => {
+ frames += 1;
+ parser.ref_frame_update(&fh).expect("ref slot update");
+ }
+ ParsedObu::Frame(f) => {
+ frames += 1;
+ parser.ref_frame_update(&f.header).expect("ref slot update");
+ }
+ _ => {}
+ }
+ }
+ }
+ // 525 is upstream's own golden (cross-checked against GStreamer's OBU walk).
+ assert_eq!(obus, 525);
+ assert_eq!(frames, 274);
+ }
+
+ #[test]
+ fn vp9_splits_superframes_and_parses_headers() {
+ let mut parser = cros_codecs::codec::vp9::parser::Parser::default();
+ let (mut chunks, mut frames) = (0u32, 0u32);
+ for packet in IvfIterator::new(VP9_25FPS) {
+ chunks += 1;
+ frames += parser
+ .parse_chunk(packet.as_ref())
+ .expect("vp9 chunk")
+ .len() as u32;
+ }
+ assert_eq!(chunks, 250);
+ // > chunks proves superframe splitting engaged.
+ assert_eq!(frames, 269);
+ }
+}
diff --git a/crates/pf-bitstream/vendor/cros-codecs/Cargo.toml b/crates/pf-bitstream/vendor/cros-codecs/Cargo.toml
new file mode 100644
index 00000000..898bfe87
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/Cargo.toml
@@ -0,0 +1,17 @@
+# Vendored snapshot — see PROVENANCE.md. Deliberately NOT opted into workspace lints
+# or workspace package inheritance: upstream code stays as close to pristine as the
+# trim allows, so re-syncing against the AOSP tree stays a diff, not an archaeology dig.
+[package]
+name = "cros-codecs"
+version = "0.0.5"
+license = "BSD-3-Clause"
+description = "Vendored cros-codecs parser layer (codec module only) for pf-bitstream"
+edition = "2021"
+
+[dependencies]
+log = "0.4"
+
+# Upstream's in-tree unit tests (kept — they are the conformance goldens) want these.
+[dev-dependencies]
+env_logger = "0.11"
+serde_json = "1"
diff --git a/crates/pf-bitstream/vendor/cros-codecs/LICENSE b/crates/pf-bitstream/vendor/cros-codecs/LICENSE
new file mode 100644
index 00000000..9c1f8394
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2022 The ChromiumOS Authors
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/crates/pf-bitstream/vendor/cros-codecs/PROVENANCE.md b/crates/pf-bitstream/vendor/cros-codecs/PROVENANCE.md
new file mode 100644
index 00000000..659b495f
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/PROVENANCE.md
@@ -0,0 +1,45 @@
+# Vendored: cros-codecs (parser layer only)
+
+- **Upstream:** (the
+ authoritative AOSP tree). Snapshot taken from the read-only GitHub mirror
+ , branch `main`,
+ commit **`5ff6d693ffae0b36935b8fc13092c733b4c2646f`**, fetched 2026-08-05.
+- **License:** BSD-3-Clause (`LICENSE`, copied verbatim). Attribution headers retained
+ in every source file.
+- **Why vendored, not a crates.io dependency:** the GitHub repo is a read-only mirror
+ and the crates.io release lags it; a pinned, reviewed snapshot is the supply-chain
+ posture punktfunk already uses elsewhere (`clients/android/native/vendor/ndk`,
+ `punktfunk-host/vendor/usbip-sim`). Decision of record:
+ punktfunk-planning `design/client-native-decode.md` §8.1.
+
+## What was taken
+
+`src/codec/{h264,h265,av1,vp9}` (parsers, DPBs, picture types, NALU/OBU machinery,
+their `test_data` vectors — they double as punktfunk's conformance corpus),
+`src/bitstream_utils.rs`, `LICENSE`. Upstream designed the `codec` module for exactly
+this extraction — its module doc: "There shall be no dependencies from other modules of
+this crate to this module, so that it can be turned into a crate of its own if needed
+in the future."
+
+## What was left behind
+
+- `decoder/`, `encoder/`, `backend/`, `c2_wrapper/`, `video_frame`, `image_processing`,
+ `utils` — the Linux-only halves (libva/v4l2/gbm/nix). punktfunk's `pf-bitstream` +
+ `pf-vkdecode` occupy that layer.
+- `codec/vp8` — VP9 has no dependency on it (verified) and no punktfunk host will ever
+ emit VP8.
+
+## Deviations from pristine upstream
+
+1. `src/lib.rs` — rewritten: keeps only the module decls and `Resolution` /
+ `ResolutionRoundMode` (the sole root items `codec` references), both copied verbatim;
+ adds crate-level `#![allow(clippy::all, mismatched_lifetime_syntaxes)]` — vendored
+ code is not held to the workspace lint bar (CI's `-D warnings` legs would fail on
+ upstream style otherwise).
+2. `src/codec.rs` — one line removed (`pub mod vp8;`).
+3. `Cargo.toml` — rewritten: `log` is the only dependency the vendored subset needs,
+ plus `env_logger`/`serde_json` dev-dependencies for upstream's in-tree tests.
+4. `cargo fmt` normalization under the workspace's rustfmt config (mechanical only).
+
+Re-sync procedure: fetch the AOSP tree, re-apply this trim, diff `codec/` +
+`bitstream_utils.rs` (expect near-zero conflicts), update the commit pin above.
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/bitstream_utils.rs b/crates/pf-bitstream/vendor/cros-codecs/src/bitstream_utils.rs
new file mode 100644
index 00000000..07d59dfe
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/bitstream_utils.rs
@@ -0,0 +1,788 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::borrow::Cow;
+use std::fmt;
+use std::io::Cursor;
+use std::io::Read;
+use std::io::Seek;
+use std::io::SeekFrom;
+use std::io::Write;
+use std::marker::PhantomData;
+
+use crate::codec::h264::parser::Nalu as H264Nalu;
+use crate::codec::h265::parser::Nalu as H265Nalu;
+
+/// A bit reader for codec bitstreams. It properly handles emulation-prevention
+/// bytes and stop bits for H264.
+#[derive(Clone)]
+pub(crate) struct BitReader<'a> {
+ /// A reference into the next unread byte in the stream.
+ data: Cursor<&'a [u8]>,
+ /// Contents of the current byte. First unread bit starting at position 8 -
+ /// num_remaining_bits_in_curr_bytes.
+ curr_byte: u8,
+ /// Number of bits remaining in `curr_byte`
+ num_remaining_bits_in_curr_byte: usize,
+ /// Used in emulation prevention byte detection.
+ prev_two_bytes: u16,
+ /// Number of emulation prevention bytes (i.e. 0x000003) we found.
+ num_epb: usize,
+ /// Whether or not we need emulation prevention logic.
+ needs_epb: bool,
+ /// How many bits have been read so far.
+ position: u64,
+}
+
+#[derive(Debug)]
+pub(crate) enum GetByteError {
+ OutOfBits,
+}
+
+impl fmt::Display for GetByteError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "reader ran out of bits")
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum ReadBitsError {
+ TooManyBitsRequested(usize),
+ GetByte(GetByteError),
+ ConversionFailed,
+}
+
+impl fmt::Display for ReadBitsError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ReadBitsError::TooManyBitsRequested(bits) => {
+ write!(f, "more than 31 ({}) bits were requested", bits)
+ }
+ ReadBitsError::GetByte(_) => write!(f, "failed to advance the current byte"),
+ ReadBitsError::ConversionFailed => {
+ write!(f, "failed to convert read input to target type")
+ }
+ }
+ }
+}
+
+impl From for ReadBitsError {
+ fn from(err: GetByteError) -> Self {
+ ReadBitsError::GetByte(err)
+ }
+}
+
+impl<'a> BitReader<'a> {
+ pub fn new(data: &'a [u8], needs_epb: bool) -> Self {
+ Self {
+ data: Cursor::new(data),
+ curr_byte: Default::default(),
+ num_remaining_bits_in_curr_byte: Default::default(),
+ prev_two_bytes: 0xffff,
+ num_epb: Default::default(),
+ needs_epb: needs_epb,
+ position: 0,
+ }
+ }
+
+ /// Read a single bit from the stream.
+ pub fn read_bit(&mut self) -> Result {
+ let bit = self.read_bits::(1)?;
+ match bit {
+ 1 => Ok(true),
+ 0 => Ok(false),
+ _ => panic!("Unexpected value {}", bit),
+ }
+ }
+
+ /// Read up to 31 bits from the stream. Note that we don't want to read 32
+ /// bits even though we're returning a u32 because that would break the
+ /// read_bits_signed() function. 31 bits should be overkill for compressed
+ /// header parsing anyway.
+ pub fn read_bits>(&mut self, num_bits: usize) -> Result {
+ if num_bits > 31 {
+ return Err(ReadBitsError::TooManyBitsRequested(num_bits).to_string());
+ }
+
+ let mut bits_left = num_bits;
+ let mut out = 0u32;
+
+ while self.num_remaining_bits_in_curr_byte < bits_left {
+ out |= (self.curr_byte as u32) << (bits_left - self.num_remaining_bits_in_curr_byte);
+ bits_left -= self.num_remaining_bits_in_curr_byte;
+ self.move_to_next_byte().map_err(|err| err.to_string())?;
+ }
+
+ out |= (self.curr_byte >> (self.num_remaining_bits_in_curr_byte - bits_left)) as u32;
+ out &= (1 << num_bits) - 1;
+ self.num_remaining_bits_in_curr_byte -= bits_left;
+ self.position += num_bits as u64;
+
+ U::try_from(out).map_err(|_| ReadBitsError::ConversionFailed.to_string())
+ }
+
+ /// Reads a two's complement signed integer of length |num_bits|.
+ pub fn read_bits_signed>(&mut self, num_bits: usize) -> Result {
+ let mut out: i32 = self
+ .read_bits::(num_bits)?
+ .try_into()
+ .map_err(|_| ReadBitsError::ConversionFailed.to_string())?;
+ if out >> (num_bits - 1) != 0 {
+ out |= -1i32 ^ ((1 << num_bits) - 1);
+ }
+
+ U::try_from(out).map_err(|_| ReadBitsError::ConversionFailed.to_string())
+ }
+
+ /// Reads an unsigned integer from the stream and checks if the stream is byte aligned.
+ pub fn read_bits_aligned>(&mut self, num_bits: usize) -> Result {
+ if self.num_remaining_bits_in_curr_byte % 8 != 0 {
+ return Err("Attempted unaligned read_le()".into());
+ }
+
+ Ok(self.read_bits(num_bits).map_err(|err| err.to_string())?)
+ }
+
+ /// Skip `num_bits` bits from the stream.
+ pub fn skip_bits(&mut self, mut num_bits: usize) -> Result<(), String> {
+ while num_bits > 0 {
+ let n = std::cmp::min(num_bits, 31);
+ self.read_bits::(n)?;
+ num_bits -= n;
+ }
+
+ Ok(())
+ }
+
+ /// Returns the amount of bits left in the stream
+ pub fn num_bits_left(&mut self) -> usize {
+ let cur_pos = self.data.position();
+ // This should always be safe to unwrap.
+ let end_pos = self.data.seek(SeekFrom::End(0)).unwrap();
+ let _ = self.data.seek(SeekFrom::Start(cur_pos));
+ ((end_pos - cur_pos) as usize) * 8 + self.num_remaining_bits_in_curr_byte
+ }
+
+ /// Returns the number of emulation-prevention bytes read so far.
+ pub fn num_epb(&self) -> usize {
+ self.num_epb
+ }
+
+ /// Whether the stream still has RBSP data. Implements more_rbsp_data(). See
+ /// the spec for more details.
+ pub fn has_more_rsbp_data(&mut self) -> bool {
+ if self.num_remaining_bits_in_curr_byte == 0 && self.move_to_next_byte().is_err() {
+ // no more data at all in the rbsp
+ return false;
+ }
+
+ // If the next bit is the stop bit, then we should only see unset bits
+ // until the end of the data.
+ if (self.curr_byte & ((1 << (self.num_remaining_bits_in_curr_byte - 1)) - 1)) != 0 {
+ return true;
+ }
+
+ let mut buf = [0u8; 1];
+ let orig_pos = self.data.position();
+ while let Ok(_) = self.data.read_exact(&mut buf) {
+ if buf[0] != 0 {
+ self.data.set_position(orig_pos);
+ return true;
+ }
+ }
+ false
+ }
+
+ /// Reads an Unsigned Exponential golomb coding number from the next bytes in the
+ /// bitstream. This may advance the state of position within the bitstream even if the
+ /// read operation is unsuccessful. See H264 Annex B specification 9.1 for details.
+ pub fn read_ue>(&mut self) -> Result {
+ let mut num_bits = 0;
+
+ while self.read_bits::(1)? == 0 {
+ num_bits += 1;
+ if num_bits > 31 {
+ return Err("invalid stream".into());
+ }
+ }
+
+ let value = ((1u32 << num_bits) - 1)
+ .checked_add(self.read_bits::(num_bits)?)
+ .ok_or::("read number cannot fit in 32 bits".into())?;
+
+ U::try_from(value).map_err(|_| "conversion error".into())
+ }
+
+ pub fn read_ue_bounded>(&mut self, min: u32, max: u32) -> Result {
+ let ue = self.read_ue()?;
+ if ue > max || ue < min {
+ Err(format!(
+ "Value out of bounds: expected {} - {}, got {}",
+ min, max, ue
+ ))
+ } else {
+ Ok(U::try_from(ue).map_err(|_| String::from("Conversion error"))?)
+ }
+ }
+
+ pub fn read_ue_max>(&mut self, max: u32) -> Result {
+ self.read_ue_bounded(0, max)
+ }
+
+ /// Reads a signed exponential golomb coding number. Instead of using two's
+ /// complement, this scheme maps even integers to positive numbers and odd
+ /// integers to negative numbers. The least significant bit indicates the
+ /// sign. See H264 Annex B specification 9.1.1 for details.
+ pub fn read_se>(&mut self) -> Result {
+ let ue = self.read_ue::()? as i32;
+
+ if ue % 2 == 0 {
+ Ok(U::try_from(-(ue / 2)).map_err(|_| String::from("Conversion error"))?)
+ } else {
+ Ok(U::try_from(ue / 2 + 1).map_err(|_| String::from("Conversion error"))?)
+ }
+ }
+
+ pub fn read_se_bounded>(&mut self, min: i32, max: i32) -> Result {
+ let se = self.read_se()?;
+ if se < min || se > max {
+ Err(format!(
+ "Value out of bounds, expected between {}-{}, got {}",
+ min, max, se
+ ))
+ } else {
+ Ok(U::try_from(se).map_err(|_| String::from("Conversion error"))?)
+ }
+ }
+
+ /// Read little endian multi-byte integer.
+ pub fn read_le>(&mut self, num_bits: u8) -> Result {
+ let mut t = 0;
+
+ for i in 0..num_bits {
+ let byte = self.read_bits_aligned::(8)?;
+ t += byte << (i * 8)
+ }
+
+ Ok(U::try_from(t).map_err(|_| String::from("Conversion error"))?)
+ }
+
+ /// Return the position of this bitstream in bits.
+ pub fn position(&self) -> u64 {
+ self.position
+ }
+
+ fn get_byte(&mut self) -> Result {
+ let mut buf = [0u8; 1];
+ self.data
+ .read_exact(&mut buf)
+ .map_err(|_| GetByteError::OutOfBits)?;
+ Ok(buf[0])
+ }
+
+ fn move_to_next_byte(&mut self) -> Result<(), GetByteError> {
+ let mut byte = self.get_byte()?;
+
+ if self.needs_epb {
+ if self.prev_two_bytes == 0 && byte == 0x03 {
+ // We found an epb
+ self.num_epb += 1;
+ // Read another byte
+ byte = self.get_byte()?;
+ // We need another 3 bytes before another epb can happen.
+ self.prev_two_bytes = 0xffff;
+ }
+ self.prev_two_bytes = (self.prev_two_bytes << 8) | u16::from(byte);
+ }
+
+ self.num_remaining_bits_in_curr_byte = 8;
+ self.curr_byte = byte;
+ Ok(())
+ }
+}
+
+/// Iterator over IVF packets.
+pub struct IvfIterator<'a> {
+ cursor: Cursor<&'a [u8]>,
+}
+
+impl<'a> IvfIterator<'a> {
+ pub fn new(data: &'a [u8]) -> Self {
+ let mut cursor = Cursor::new(data);
+
+ // Skip the IVH header entirely.
+ cursor.seek(std::io::SeekFrom::Start(32)).unwrap();
+
+ Self { cursor }
+ }
+}
+
+impl<'a> Iterator for IvfIterator<'a> {
+ type Item = &'a [u8];
+
+ fn next(&mut self) -> Option {
+ // Make sure we have a header.
+ let mut len_buf = [0u8; 4];
+ self.cursor.read_exact(&mut len_buf).ok()?;
+ let len = ((len_buf[3] as usize) << 24)
+ | ((len_buf[2] as usize) << 16)
+ | ((len_buf[1] as usize) << 8)
+ | (len_buf[0] as usize);
+
+ // Skip PTS.
+ self.cursor.seek(std::io::SeekFrom::Current(8)).ok()?;
+
+ let start = self.cursor.position() as usize;
+ let _ = self
+ .cursor
+ .seek(std::io::SeekFrom::Current(len as i64))
+ .ok()?;
+ let end = self.cursor.position() as usize;
+
+ Some(&self.cursor.get_ref()[start..end])
+ }
+}
+
+/// Helper struct for synthesizing IVF file header
+pub struct IvfFileHeader {
+ pub magic: [u8; 4],
+ pub version: u16,
+ pub header_size: u16,
+ pub codec: [u8; 4],
+ pub width: u16,
+ pub height: u16,
+ pub framerate: u32,
+ pub timescale: u32,
+ pub frame_count: u32,
+ pub unused: u32,
+}
+
+impl Default for IvfFileHeader {
+ fn default() -> Self {
+ Self {
+ magic: Self::MAGIC,
+ version: 0,
+ header_size: 32,
+ codec: Self::CODEC_VP9,
+ width: 320,
+ height: 240,
+ framerate: 1,
+ timescale: 1000,
+ frame_count: 1,
+ unused: Default::default(),
+ }
+ }
+}
+
+impl IvfFileHeader {
+ pub const MAGIC: [u8; 4] = *b"DKIF";
+ pub const CODEC_VP8: [u8; 4] = *b"VP80";
+ pub const CODEC_VP9: [u8; 4] = *b"VP90";
+ pub const CODEC_AV1: [u8; 4] = *b"AV01";
+
+ pub fn new(codec: [u8; 4], width: u16, height: u16, framerate: u32, frame_count: u32) -> Self {
+ let default = Self::default();
+
+ Self {
+ codec,
+ width,
+ height,
+ framerate: framerate * default.timescale,
+ frame_count,
+ ..default
+ }
+ }
+}
+
+impl IvfFileHeader {
+ /// Writes header into writer
+ pub fn writo_into(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
+ writer.write_all(&self.magic)?;
+ writer.write_all(&self.version.to_le_bytes())?;
+ writer.write_all(&self.header_size.to_le_bytes())?;
+ writer.write_all(&self.codec)?;
+ writer.write_all(&self.width.to_le_bytes())?;
+ writer.write_all(&self.height.to_le_bytes())?;
+ writer.write_all(&self.framerate.to_le_bytes())?;
+ writer.write_all(&self.timescale.to_le_bytes())?;
+ writer.write_all(&self.frame_count.to_le_bytes())?;
+ writer.write_all(&self.unused.to_le_bytes())?;
+
+ Ok(())
+ }
+}
+
+/// Helper struct for synthesizing IVF frame header
+pub struct IvfFrameHeader {
+ pub frame_size: u32,
+ pub timestamp: u64,
+}
+
+impl IvfFrameHeader {
+ /// Writes header into writer
+ pub fn writo_into(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
+ writer.write_all(&self.frame_size.to_le_bytes())?;
+ writer.write_all(&self.timestamp.to_le_bytes())?;
+ Ok(())
+ }
+}
+
+/// Iterator NALUs in a bitstream.
+pub struct NalIterator<'a, Nalu>(Cursor<&'a [u8]>, PhantomData);
+
+impl<'a, Nalu> NalIterator<'a, Nalu> {
+ pub fn new(stream: &'a [u8]) -> Self {
+ Self(Cursor::new(stream), PhantomData)
+ }
+}
+
+impl<'a> Iterator for NalIterator<'a, H264Nalu<'a>> {
+ type Item = Cow<'a, [u8]>;
+
+ fn next(&mut self) -> Option {
+ H264Nalu::next(&mut self.0).map(|n| n.data).ok()
+ }
+}
+
+impl<'a> Iterator for NalIterator<'a, H265Nalu<'a>> {
+ type Item = Cow<'a, [u8]>;
+
+ fn next(&mut self) -> Option {
+ H265Nalu::next(&mut self.0).map(|n| n.data).ok()
+ }
+}
+
+#[derive(Debug)]
+pub enum BitWriterError {
+ InvalidBitCount,
+ Io(std::io::Error),
+}
+
+impl fmt::Display for BitWriterError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ BitWriterError::InvalidBitCount => write!(f, "invalid bit count"),
+ BitWriterError::Io(x) => write!(f, "{}", x.to_string()),
+ }
+ }
+}
+
+impl From for BitWriterError {
+ fn from(err: std::io::Error) -> Self {
+ BitWriterError::Io(err)
+ }
+}
+
+pub type BitWriterResult = std::result::Result;
+
+pub struct BitWriter {
+ out: W,
+ nth_bit: u8,
+ curr_byte: u8,
+}
+
+impl BitWriter {
+ pub fn new(writer: W) -> Self {
+ Self {
+ out: writer,
+ curr_byte: 0,
+ nth_bit: 0,
+ }
+ }
+
+ /// Writes fixed bit size integer (up to 32 bit)
+ pub fn write_f>(&mut self, bits: usize, value: T) -> BitWriterResult {
+ let value = value.into();
+
+ if bits > 32 {
+ return Err(BitWriterError::InvalidBitCount);
+ }
+
+ let mut written = 0;
+ for bit in (0..bits).rev() {
+ let bit = (1 << bit) as u32;
+
+ self.write_bit((value & bit) == bit)?;
+ written += 1;
+ }
+
+ Ok(written)
+ }
+
+ /// Takes a single bit that will be outputed to [`std::io::Write`]
+ pub fn write_bit(&mut self, bit: bool) -> BitWriterResult<()> {
+ self.curr_byte |= (bit as u8) << (7u8 - self.nth_bit);
+ self.nth_bit += 1;
+
+ if self.nth_bit == 8 {
+ self.out.write_all(&[self.curr_byte])?;
+ self.nth_bit = 0;
+ self.curr_byte = 0;
+ }
+
+ Ok(())
+ }
+
+ /// Immediately outputs any cached bits to [`std::io::Write`]
+ pub fn flush(&mut self) -> BitWriterResult<()> {
+ if self.nth_bit != 0 {
+ self.out.write_all(&[self.curr_byte])?;
+ self.nth_bit = 0;
+ self.curr_byte = 0;
+ }
+
+ self.out.flush()?;
+ Ok(())
+ }
+
+ /// Returns `true` if ['Self`] hold data that wasn't written to [`std::io::Write`]
+ pub fn has_data_pending(&self) -> bool {
+ self.nth_bit != 0
+ }
+
+ pub(crate) fn inner(&self) -> &W {
+ &self.out
+ }
+
+ pub(crate) fn inner_mut(&mut self) -> &mut W {
+ &mut self.out
+ }
+}
+
+impl Drop for BitWriter {
+ fn drop(&mut self) {
+ if let Err(e) = self.flush() {
+ log::error!("Unable to flush bits {e:?}");
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_ivf_file_header() {
+ let mut hdr = IvfFileHeader {
+ version: 0,
+ codec: IvfFileHeader::CODEC_VP9,
+ width: 256,
+ height: 256,
+ framerate: 30_000,
+ timescale: 1_000,
+ frame_count: 1,
+
+ ..Default::default()
+ };
+
+ let mut buf = Vec::new();
+ hdr.writo_into(&mut buf).unwrap();
+
+ const EXPECTED: [u8; 32] = [
+ 0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x00, 0x01,
+ 0x00, 0x01, 0x30, 0x75, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ assert_eq!(&buf, &EXPECTED);
+
+ hdr.width = 1920;
+ hdr.height = 800;
+ hdr.framerate = 24;
+ hdr.timescale = 1;
+ hdr.frame_count = 100;
+
+ buf.clear();
+ hdr.writo_into(&mut buf).unwrap();
+
+ const EXPECTED2: [u8; 32] = [
+ 0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x80, 0x07,
+ 0x20, 0x03, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ assert_eq!(&buf, &EXPECTED2);
+ }
+
+ #[test]
+ fn test_ivf_frame_header() {
+ let mut hdr = IvfFrameHeader {
+ frame_size: 199249,
+ timestamp: 0,
+ };
+
+ let mut buf = Vec::new();
+ hdr.writo_into(&mut buf).unwrap();
+
+ const EXPECTED: [u8; 12] = [
+ 0x51, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ assert_eq!(&buf, &EXPECTED);
+
+ hdr.timestamp = 1;
+ hdr.frame_size = 52;
+
+ buf.clear();
+ hdr.writo_into(&mut buf).unwrap();
+
+ const EXPECTED2: [u8; 12] = [
+ 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ assert_eq!(&buf, &EXPECTED2);
+ }
+
+ #[test]
+ fn test_bitwriter_f1() {
+ let mut buf = Vec::::new();
+ {
+ let mut writer = BitWriter::new(&mut buf);
+ writer.write_f(1, true).unwrap();
+ writer.write_f(1, false).unwrap();
+ writer.write_f(1, false).unwrap();
+ writer.write_f(1, false).unwrap();
+ writer.write_f(1, true).unwrap();
+ writer.write_f(1, true).unwrap();
+ writer.write_f(1, true).unwrap();
+ writer.write_f(1, true).unwrap();
+ }
+ assert_eq!(buf, vec![0b10001111u8]);
+ }
+
+ #[test]
+ fn test_bitwriter_f3() {
+ let mut buf = Vec::::new();
+ {
+ let mut writer = BitWriter::new(&mut buf);
+ writer.write_f(3, 0b100u8).unwrap();
+ writer.write_f(3, 0b101u8).unwrap();
+ writer.write_f(3, 0b011u8).unwrap();
+ }
+ assert_eq!(buf, vec![0b10010101u8, 0b10000000u8]);
+ }
+
+ #[test]
+ fn test_bitwriter_f4() {
+ let mut buf = Vec::::new();
+ {
+ let mut writer = BitWriter::new(&mut buf);
+ writer.write_f(4, 0b1000u8).unwrap();
+ writer.write_f(4, 0b1011u8).unwrap();
+ }
+ assert_eq!(buf, vec![0b10001011u8]);
+ }
+
+ // These tests are adapted from the chromium tests at media/video/h264_bit_reader_unitttest.cc
+
+ #[test]
+ fn read_stream_without_escape_and_trailing_zero_bytes() {
+ const RBSP: [u8; 6] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xa0];
+
+ let mut reader = BitReader::new(&RBSP, true);
+ assert_eq!(reader.read_bits::(1).unwrap(), 0);
+ assert_eq!(reader.num_bits_left(), 47);
+ assert!(reader.has_more_rsbp_data());
+
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x02);
+ assert_eq!(reader.num_bits_left(), 39);
+ assert!(reader.has_more_rsbp_data());
+
+ assert_eq!(reader.read_bits::(31).unwrap(), 0x23456789);
+ assert_eq!(reader.num_bits_left(), 8);
+ assert!(reader.has_more_rsbp_data());
+
+ assert_eq!(reader.read_bits::(1).unwrap(), 1);
+ assert_eq!(reader.num_bits_left(), 7);
+ assert!(reader.has_more_rsbp_data());
+
+ assert_eq!(reader.read_bits::(1).unwrap(), 0);
+ assert_eq!(reader.num_bits_left(), 6);
+ assert!(!reader.has_more_rsbp_data());
+ }
+
+ #[test]
+ fn single_byte_stream() {
+ const RBSP: [u8; 1] = [0x18];
+
+ let mut reader = BitReader::new(&RBSP, true);
+ assert_eq!(reader.num_bits_left(), 8);
+ assert!(reader.has_more_rsbp_data());
+ assert_eq!(reader.read_bits::(4).unwrap(), 1);
+ assert!(!reader.has_more_rsbp_data());
+ }
+
+ #[test]
+ fn stop_bit_occupy_full_byte() {
+ const RBSP: [u8; 2] = [0xab, 0x80];
+
+ let mut reader = BitReader::new(&RBSP, true);
+ assert_eq!(reader.num_bits_left(), 16);
+ assert!(reader.has_more_rsbp_data());
+
+ assert_eq!(reader.read_bits::(8).unwrap(), 0xab);
+ assert_eq!(reader.num_bits_left(), 8);
+
+ assert!(!reader.has_more_rsbp_data());
+ }
+
+ // Check that read_ue behaves properly with input at the limits.
+ #[test]
+ fn read_ue() {
+ // Regular value.
+ let mut reader = BitReader::new(&[0b0001_1010], true);
+ assert_eq!(reader.read_ue::().unwrap(), 12);
+ assert_eq!(reader.data.position(), 1);
+ assert_eq!(reader.num_remaining_bits_in_curr_byte, 1);
+
+ // 0 value.
+ let mut reader = BitReader::new(&[0b1000_0000], true);
+ assert_eq!(reader.read_ue::().unwrap(), 0);
+ assert_eq!(reader.data.position(), 1);
+ assert_eq!(reader.num_remaining_bits_in_curr_byte, 7);
+
+ // No prefix stop bit.
+ let mut reader = BitReader::new(&[0b0000_0000], true);
+ reader.read_ue::().unwrap_err();
+
+ // u32 max value: 31 0-bits, 1 bit marker, 31 bits 1-bits.
+ let mut reader = BitReader::new(
+ &[
+ 0b0000_0000,
+ 0b0000_0000,
+ 0b0000_0000,
+ 0b0000_0001,
+ 0b1111_1111,
+ 0b1111_1111,
+ 0b1111_1111,
+ 0b1111_1110,
+ ],
+ true,
+ );
+ assert_eq!(reader.read_ue::().unwrap(), 0xffff_fffe);
+ assert_eq!(reader.data.position(), 8);
+ assert_eq!(reader.num_remaining_bits_in_curr_byte, 1);
+ }
+
+ // Check that emulation prevention is being handled correctly.
+ #[test]
+ fn skip_epb_when_enabled() {
+ let mut reader = BitReader::new(&[0x00, 0x00, 0x03, 0x01], false);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x00);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x00);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x03);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x01);
+
+ let mut reader = BitReader::new(&[0x00, 0x00, 0x03, 0x01], true);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x00);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x00);
+ assert_eq!(reader.read_bits::(8).unwrap(), 0x01);
+ }
+
+ #[test]
+ fn read_signed_bits() {
+ let mut reader = BitReader::new(&[0b1111_0000], false);
+ assert_eq!(reader.read_bits_signed::(4).unwrap(), -1);
+ }
+}
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/codec.rs b/crates/pf-bitstream/vendor/cros-codecs/src/codec.rs
new file mode 100644
index 00000000..23876f40
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/codec.rs
@@ -0,0 +1,17 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Parsers for various kinds of encoded streams.
+//!
+//! This module does not provide any actual decoding tools - that's the job of the
+//! [crate::decoder] module. However the parsers of this module are heavily used in order to
+//! implement stateless decoding.
+//!
+//! There shall be no dependencies from other modules of this crate to this module, so that it
+//! can be turned into a crate of its own if needed in the future.
+
+pub mod av1;
+pub mod h264;
+pub mod h265;
+pub mod vp9;
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1.rs b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1.rs
new file mode 100644
index 00000000..477cd906
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1.rs
@@ -0,0 +1,9 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod helpers;
+pub mod parser;
+pub mod reader;
+pub mod synthesizer;
+pub mod writer;
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/helpers.rs b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/helpers.rs
new file mode 100644
index 00000000..f8df7cd8
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/helpers.rs
@@ -0,0 +1,186 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::codec::av1::parser::NUM_REF_FRAMES;
+
+const DIV_LUT: [i32; 257] = [
+ 16384, 16320, 16257, 16194, 16132, 16070, 16009, 15948, 15888, 15828, 15768, 15709, 15650,
+ 15592, 15534, 15477, 15420, 15364, 15308, 15252, 15197, 15142, 15087, 15033, 14980, 14926,
+ 14873, 14821, 14769, 14717, 14665, 14614, 14564, 14513, 14463, 14413, 14364, 14315, 14266,
+ 14218, 14170, 14122, 14075, 14028, 13981, 13935, 13888, 13843, 13797, 13752, 13707, 13662,
+ 13618, 13574, 13530, 13487, 13443, 13400, 13358, 13315, 13273, 13231, 13190, 13148, 13107,
+ 13066, 13026, 12985, 12945, 12906, 12866, 12827, 12788, 12749, 12710, 12672, 12633, 12596,
+ 12558, 12520, 12483, 12446, 12409, 12373, 12336, 12300, 12264, 12228, 12193, 12157, 12122,
+ 12087, 12053, 12018, 11984, 11950, 11916, 11882, 11848, 11815, 11782, 11749, 11716, 11683,
+ 11651, 11619, 11586, 11555, 11523, 11491, 11460, 11429, 11398, 11367, 11336, 11305, 11275,
+ 11245, 11215, 11185, 11155, 11125, 11096, 11067, 11038, 11009, 10980, 10951, 10923, 10894,
+ 10866, 10838, 10810, 10782, 10755, 10727, 10700, 10673, 10645, 10618, 10592, 10565, 10538,
+ 10512, 10486, 10460, 10434, 10408, 10382, 10356, 10331, 10305, 10280, 10255, 10230, 10205,
+ 10180, 10156, 10131, 10107, 10082, 10058, 10034, 10010, 9986, 9963, 9939, 9916, 9892, 9869,
+ 9846, 9823, 9800, 9777, 9754, 9732, 9709, 9687, 9664, 9642, 9620, 9598, 9576, 9554, 9533, 9511,
+ 9489, 9468, 9447, 9425, 9404, 9383, 9362, 9341, 9321, 9300, 9279, 9259, 9239, 9218, 9198, 9178,
+ 9158, 9138, 9118, 9098, 9079, 9059, 9039, 9020, 9001, 8981, 8962, 8943, 8924, 8905, 8886, 8867,
+ 8849, 8830, 8812, 8793, 8775, 8756, 8738, 8720, 8702, 8684, 8666, 8648, 8630, 8613, 8595, 8577,
+ 8560, 8542, 8525, 8508, 8490, 8473, 8456, 8439, 8422, 8405, 8389, 8372, 8355, 8339, 8322, 8306,
+ 8289, 8273, 8257, 8240, 8224, 8208, 8192,
+];
+
+const DIV_LUT_BITS: u32 = 8;
+const DIV_LUT_PREC_BITS: u32 = 14;
+
+/// Implements FloorLog2(x), which is defined to be the floor of the base 2
+/// logarithm of the input x.
+///
+/// The input x will always be an integer, and will always be greater than or equal to 1.
+/// This function extracts the location of the most significant bit in x.
+pub fn floor_log2(mut x: u32) -> u32 {
+ assert!(x > 0);
+ let mut s = 0;
+
+ while x != 0 {
+ x >>= 1;
+ s += 1;
+ }
+
+ s - 1
+}
+
+/// Implements 5.9.3. Get relative distance function
+pub fn get_relative_dist(enable_order_hint: bool, order_hint_bits: i32, a: i32, b: i32) -> i32 {
+ if !enable_order_hint {
+ 0
+ } else {
+ let diff = a - b;
+ let m = 1 << (order_hint_bits - 1);
+ (diff & (m - 1)) - (diff & m)
+ }
+}
+
+/// Implements find_latest_backward from section 7.8.
+pub fn find_latest_backward(
+ shifted_order_hints: &[i32; NUM_REF_FRAMES],
+ used_frame: &[bool; NUM_REF_FRAMES],
+ cur_frame_hint: i32,
+ latest_order_hint: &mut i32,
+) -> i32 {
+ let mut _ref = -1;
+
+ for i in 0..NUM_REF_FRAMES {
+ let hint = shifted_order_hints[i];
+ if !used_frame[i] && hint >= cur_frame_hint && (_ref < 0 || hint >= *latest_order_hint) {
+ _ref = i as i32;
+ *latest_order_hint = hint;
+ }
+ }
+
+ _ref
+}
+
+/// Implements find_earliest_backward from section 7.8.
+pub fn find_earliest_backward(
+ shifted_order_hints: &[i32; NUM_REF_FRAMES],
+ used_frame: &[bool; NUM_REF_FRAMES],
+ cur_frame_hint: i32,
+ earliest_order_hint: &mut i32,
+) -> i32 {
+ let mut _ref = -1;
+
+ for i in 0..NUM_REF_FRAMES {
+ let hint = shifted_order_hints[i];
+ if !used_frame[i] && hint >= cur_frame_hint && (_ref < 0 || hint < *earliest_order_hint) {
+ _ref = i as i32;
+ *earliest_order_hint = hint;
+ }
+ }
+
+ _ref
+}
+
+/// Implements find_latest_forward from section 7.8.
+pub fn find_latest_forward(
+ shifted_order_hints: &[i32; NUM_REF_FRAMES],
+ used_frame: &[bool; NUM_REF_FRAMES],
+ cur_frame_hint: i32,
+ latest_order_hint: &mut i32,
+) -> i32 {
+ let mut _ref = -1;
+ for i in 0..NUM_REF_FRAMES {
+ let hint = shifted_order_hints[i];
+ if !used_frame[i] && hint < cur_frame_hint && (_ref < 0 || hint >= *latest_order_hint) {
+ _ref = i as i32;
+ *latest_order_hint = hint;
+ }
+ }
+
+ _ref
+}
+
+pub fn tile_log2(blk_size: u32, target: u32) -> u32 {
+ let mut k = 0;
+
+ while (blk_size << k) < target {
+ k += 1;
+ }
+
+ k
+}
+
+pub fn clip3(x: i32, y: i32, z: i32) -> i32 {
+ if z < x {
+ x
+ } else if z > y {
+ y
+ } else {
+ z
+ }
+}
+
+/// 5.9.29
+pub 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)
+ }
+}
+
+/// Implements Round2. See 4.7: mathematical functions.
+pub fn round2(x: u32, n: u32) -> u32 {
+ (x + 2u32.pow(n - 1)) / 2u32.pow(n)
+}
+
+/// Implements Round2Signed. See 4.7: mathematical functions.
+pub fn round2signed(x: i32, n: u32) -> Result {
+ if x >= 0 {
+ i32::try_from(round2(x as u32, n)).map_err(|e| e.to_string())
+ } else {
+ let x = x as i64;
+ let val = i32::try_from(round2(-x as u32, n)).map_err(|e| e.to_string())?;
+ Ok(-val)
+ }
+}
+
+/// Implements 7.11.3.7. Resolve divisor process
+pub fn resolve_divisor(d: i32) -> Result<(u32, i32), String> {
+ let abs_d = u32::try_from(d.abs()).unwrap(); // abs cannot return a negative
+ let n = floor_log2(abs_d);
+ let e = abs_d - (1 << n);
+
+ let f = if n > DIV_LUT_BITS {
+ round2(e, n - DIV_LUT_BITS)
+ } else {
+ e << (DIV_LUT_BITS - n)
+ };
+
+ let div_shift = n + DIV_LUT_PREC_BITS;
+ let div_factor = if d < 0 {
+ -DIV_LUT[f as usize]
+ } else {
+ DIV_LUT[f as usize]
+ };
+
+ Ok((div_shift, div_factor))
+}
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/parser.rs b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/parser.rs
new file mode 100644
index 00000000..ecb40a9f
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/parser.rs
@@ -0,0 +1,4294 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::borrow::Cow;
+use std::rc::Rc;
+
+use crate::codec::av1::helpers;
+use crate::codec::av1::reader::Reader;
+
+pub const TOTAL_REFS_PER_FRAME: usize = 8;
+pub const NUM_REF_FRAMES: usize = 8;
+pub const REFS_PER_FRAME: usize = 7;
+pub const MAX_SEGMENTS: usize = 8;
+pub const SEG_LVL_ALT_Q: usize = 0;
+pub const SEG_LVL_ALT_LF_Y_V: usize = 1;
+pub const SEG_LVL_REF_FRAME: usize = 5;
+pub const SEG_LVL_SKIP: usize = 6;
+pub const SEG_LVL_GLOBAL_MV: usize = 7;
+pub const SEG_LVL_MAX: usize = 8;
+pub const MAX_TILE_COLS: usize = 64;
+pub const MAX_TILE_ROWS: usize = 64;
+pub const CDEF_MAX: usize = 1 << 3;
+pub const MAX_NUM_PLANES: usize = 3;
+pub const MAX_NUM_Y_POINTS: usize = 16;
+pub const MAX_NUM_CB_POINTS: usize = 16;
+pub const MAX_NUM_CR_POINTS: usize = 16;
+pub const MAX_NUM_POS_LUMA: usize = 25;
+pub const MAX_NUM_SPATIAL_LAYERS: usize = 4;
+pub const MAX_NUM_TEMPORAL_LAYERS: usize = 8;
+pub const MAX_NUM_OPERATING_POINTS: usize = MAX_NUM_SPATIAL_LAYERS * MAX_NUM_TEMPORAL_LAYERS;
+pub const SELECT_SCREEN_CONTENT_TOOLS: usize = 2;
+pub const SELECT_INTEGER_MV: usize = 2;
+pub const PRIMARY_REF_NONE: u32 = 7;
+pub const SUPERRES_DENOM_BITS: usize = 3;
+pub const SUPERRES_DENOM_MIN: usize = 9;
+pub const SUPERRES_NUM: usize = 8;
+pub const MAX_TILE_WIDTH: u32 = 4096;
+pub const MAX_TILE_HEIGHT: u32 = 2304;
+pub const MAX_TILE_AREA: u32 = MAX_TILE_WIDTH * MAX_TILE_HEIGHT;
+pub const RESTORATION_TILESIZE_MAX: u16 = 256;
+pub const WARPEDMODEL_PREC_BITS: u32 = 16;
+pub const WARP_PARAM_REDUCE_BITS: u32 = 6;
+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;
+
+// Same as Segmentation_Feature_Bits in the specification. See 5.9.14
+pub const FEATURE_BITS: [u8; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0];
+// Same as Segmentation_Feature_Signed in the specification. See 5.9.14
+pub const FEATURE_SIGNED: [bool; SEG_LVL_MAX] = [true, true, true, true, true, false, false, false];
+// Same as Segmentation_Feature_Max in the specification. See 5.9.14
+pub const FEATURE_MAX: [i32; SEG_LVL_MAX] = [255, 63, 63, 63, 63, 7, 0, 0];
+
+/// Tells what should be done with the OBU after [`Parser::read_obu`] is called.
+pub enum ObuAction<'a> {
+ /// We should process the OBU normally.
+ Process(Obu<'a>),
+ /// We should drop this OBU and advance to the next one. The u32 is how much
+ /// we should advance.
+ Drop(u32),
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ObuType {
+ #[default]
+ Reserved = 0,
+ SequenceHeader = 1,
+ TemporalDelimiter = 2,
+ FrameHeader = 3,
+ TileGroup = 4,
+ Metadata = 5,
+ Frame = 6,
+ RedundantFrameHeader = 7,
+ TileList = 8,
+ Reserved2 = 9,
+ Reserved3 = 10,
+ Reserved4 = 11,
+ Reserved5 = 12,
+ Reserved6 = 13,
+ Reserved7 = 14,
+ Padding = 15,
+}
+
+impl TryFrom for ObuType {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(ObuType::Reserved),
+ 1 => Ok(ObuType::SequenceHeader),
+ 2 => Ok(ObuType::TemporalDelimiter),
+ 3 => Ok(ObuType::FrameHeader),
+ 4 => Ok(ObuType::TileGroup),
+ 5 => Ok(ObuType::Metadata),
+ 6 => Ok(ObuType::Frame),
+ 7 => Ok(ObuType::RedundantFrameHeader),
+ 8 => Ok(ObuType::TileList),
+ 9 => Ok(ObuType::Reserved2),
+ 10 => Ok(ObuType::Reserved3),
+ 11 => Ok(ObuType::Reserved4),
+ 12 => Ok(ObuType::Reserved5),
+ 13 => Ok(ObuType::Reserved6),
+ 14 => Ok(ObuType::Reserved7),
+ 15 => Ok(ObuType::Padding),
+ _ => Err(format!("Invalid ObuType {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Profile {
+ #[default]
+ Profile0 = 0,
+ Profile1 = 1,
+ Profile2 = 2,
+}
+
+impl TryFrom for Profile {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(Profile::Profile0),
+ 1 => Ok(Profile::Profile1),
+ 2 => Ok(Profile::Profile2),
+ _ => Err(format!("Invalid Profile {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct ObuHeader {
+ pub obu_type: ObuType,
+ pub extension_flag: bool,
+ pub has_size_field: bool,
+ pub temporal_id: u32,
+ pub spatial_id: u32,
+}
+
+impl ObuHeader {
+ /// Length in bytes
+ #[allow(clippy::len_without_is_empty)]
+ pub fn len(&self) -> usize {
+ if self.extension_flag {
+ 2
+ } else {
+ 1
+ }
+ }
+}
+
+/// Contains the OBU header and a reference to its data. The OBU itself hasn't been parsed yet.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Obu<'a> {
+ /// The OBU header.
+ pub header: ObuHeader,
+ /// Amount of bytes from the input consumed to parse this OBU.
+ pub bytes_used: usize,
+ /// The slice backing the OBU.
+ data: Cow<'a, [u8]>,
+}
+
+impl<'a> AsRef<[u8]> for Obu<'a> {
+ fn as_ref(&self) -> &[u8] {
+ self.data.as_ref()
+ }
+}
+
+/// A fully parsed OBU, with additional data when relevant.
+pub enum ParsedObu<'a> {
+ Reserved,
+ SequenceHeader(Rc),
+ TemporalDelimiter,
+ FrameHeader(FrameHeaderObu),
+ TileGroup(TileGroupObu<'a>),
+ Metadata,
+ Frame(FrameObu<'a>),
+ RedundantFrameHeader,
+ TileList,
+ Reserved2,
+ Reserved3,
+ Reserved4,
+ Reserved5,
+ Reserved6,
+ Reserved7,
+ Padding,
+}
+
+impl<'a> ParsedObu<'a> {
+ pub fn obu_type(&self) -> ObuType {
+ match self {
+ ParsedObu::Reserved => ObuType::Reserved,
+ ParsedObu::SequenceHeader(_) => ObuType::SequenceHeader,
+ ParsedObu::TemporalDelimiter => ObuType::TemporalDelimiter,
+ ParsedObu::FrameHeader(_) => ObuType::FrameHeader,
+ ParsedObu::TileGroup(_) => ObuType::TileGroup,
+ ParsedObu::Metadata => ObuType::Metadata,
+ ParsedObu::Frame(_) => ObuType::Frame,
+ ParsedObu::RedundantFrameHeader => ObuType::RedundantFrameHeader,
+ ParsedObu::TileList => ObuType::TileList,
+ ParsedObu::Reserved2 => ObuType::Reserved2,
+ ParsedObu::Reserved3 => ObuType::Reserved3,
+ ParsedObu::Reserved4 => ObuType::Reserved4,
+ ParsedObu::Reserved5 => ObuType::Reserved5,
+ ParsedObu::Reserved6 => ObuType::Reserved6,
+ ParsedObu::Reserved7 => ObuType::Reserved7,
+ ParsedObu::Padding => ObuType::Padding,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Tile {
+ /// Same as TileOffset in the specification.
+ pub tile_offset: u32,
+ /// Same as TileSize in the specification.
+ pub tile_size: u32,
+ /// Same as TileRow in the specification.
+ pub tile_row: u32,
+ /// Same as TileCol in the specification.
+ pub tile_col: u32,
+ // Same as MiRowStart in the specification.
+ pub mi_row_start: u32,
+ // Same as MiRowEnd in the specification.
+ pub mi_row_end: u32,
+ // Same as MiColStart in the specification.
+ pub mi_col_start: u32,
+ // Same as MiColEnd in the specification.
+ pub mi_col_end: u32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct TileGroupObu<'a> {
+ /// The OBU backing this tile group.
+ pub obu: Obu<'a>,
+ /// Specifies whether tg_start and tg_end are present. If tg_start and
+ /// tg_end are not present, this tile group covers the entire frame.
+ pub tile_start_and_end_present_flag: bool,
+ /// Specifies the zero-based index of the first tile in the current tile
+ /// group.
+ pub tg_start: u32,
+ /// Specifies the zero-based index of the last tile in the current tile
+ /// group.
+ pub tg_end: u32,
+ /// Contains the tiles in this tile group. Use `tile_offset`to index into
+ /// the OBU data.
+ ///
+ /// The tiles in the Vec span from tg_start to tg_end.
+ pub tiles: Vec,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct OperatingPoint {
+ /// Specifies the level that the coded video sequence conforms to when
+ /// operating point i is selected.
+ pub seq_level_idx: u8,
+ /// Specifies the tier that the coded video sequence conforms to when
+ /// operating point i is selected.
+ pub seq_tier: u8,
+ /// Specifies the value of operating_point_idc for the selected operating
+ /// point.
+ pub idc: u16,
+ /// If set, indicates that there is a decoder model associated with
+ /// operating point i. If not set, indicates that there is not a decoder
+ /// model associated with operating point i.
+ pub decoder_model_present_for_this_op: bool,
+ /// Specifies the time interval between the arrival of the first bit in the
+ /// smoothing buffer and the subsequent removal of the data that belongs to
+ /// the first coded frame for operating point op, measured in units of
+ /// 1/90000 seconds. The length of decoder_buffer_delay is specified by
+ /// buffer_delay_length_minus_1 + 1, in bits.
+ pub decoder_buffer_delay: u32,
+ /// Specifies, in combination with decoder_buffer_delay\[ op \] syntax
+ /// element, the first bit arrival time of frames to be decoded to the
+ /// smoothing buffer. encoder_buffer_delay is measured in units of 1/90000
+ /// seconds.
+ pub encoder_buffer_delay: u32,
+ /// If set, indicates that the smoothing buffer operates in low-delay mode
+ /// for operating point op. In low-delay mode late decode times and buffer
+ /// underflow are both permitted. If not set, indicates that the smoothing
+ /// buffer operates in strict mode, where buffer underflow is not allowed.
+ pub low_delay_mode_flag: bool,
+ /// If set, indicates that initial_display_delay_minus_1 is specified for
+ /// operating point i. If not set, indicates that
+ /// initial_display_delay_minus_1 is not specified for operating point i.
+ pub initial_display_delay_present_for_this_op: bool,
+ /// Plus 1 specifies, for operating point i, the number of decoded frames
+ /// that should be present in the buffer pool before the first presentable
+ /// frame is displayed. This will ensure that all presentable frames in the
+ /// sequence can be decoded at or before the time that they are scheduled
+ /// for display. If not signaled then initial_display_delay_minus_1\[ i \] =
+ /// BUFFER_POOL_MAX_SIZE - 1.
+ pub initial_display_delay_minus_1: u32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct TimingInfo {
+ /// The number of time units of a clock operating at the frequency
+ /// time_scale Hz that corresponds to one increment of a clock tick counter.
+ /// A display clock tick, in seconds, is equal to num_units_in_display_tick
+ /// divided by time_scale:
+ pub num_units_in_display_tick: u32,
+ /// The number of time units that pass in one second.
+ pub time_scale: u32,
+ /// If set, indicates that pictures should be displayed according to their
+ /// output order with the number of ticks between two consecutive pictures
+ /// (without dropping frames) specified by num_ticks_per_picture_minus_1 +
+ /// 1. If not set, indicates that the interval between two consecutive
+ /// pictures is not specified.
+ pub equal_picture_interval: bool,
+ /// Plus 1 specifies the number of clock ticks corresponding to output time
+ /// between two consecutive pictures in the output order.
+ pub num_ticks_per_picture_minus_1: u32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct DecoderModelInfo {
+ /// Plus 1 specifies the length of the decoder_buffer_delay and the
+ /// encoder_buffer_delay syntax elements, in bits.
+ pub buffer_delay_length_minus_1: u8,
+ /// The number of time units of a decoding clock operating at the frequency
+ /// time_scale Hz that corresponds to one increment of a clock tick counter:
+ pub num_units_in_decoding_tick: u32,
+ /// Plus 1 specifies the length of the buffer_removal_time syntax element,
+ /// in bits.
+ pub buffer_removal_time_length_minus_1: u8,
+ /// Plus 1 specifies the length of the frame_presentation_time syntax
+ /// element, in bits.
+ pub frame_presentation_time_length_minus_1: u32,
+}
+
+/// Defined by the “Color primaries” section of ISO/IEC 23091-4/ITU-T H.273
+/// See 6.4.2
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ColorPrimaries {
+ Bt709 = 1,
+ #[default]
+ Unspecified = 2,
+ Bt470M = 4,
+ Bt470bg = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ GenericFilm = 8,
+ Bt2020 = 9,
+ Xyz = 10,
+ Smpte431 = 11,
+ Smpte432 = 12,
+ Ebu3213 = 22,
+}
+
+impl TryFrom for ColorPrimaries {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 1 => Ok(ColorPrimaries::Bt709),
+ 2 => Ok(ColorPrimaries::Unspecified),
+ 4 => Ok(ColorPrimaries::Bt470M),
+ 5 => Ok(ColorPrimaries::Bt470bg),
+ 6 => Ok(ColorPrimaries::Bt601),
+ 7 => Ok(ColorPrimaries::Smpte240),
+ 8 => Ok(ColorPrimaries::GenericFilm),
+ 9 => Ok(ColorPrimaries::Bt2020),
+ 10 => Ok(ColorPrimaries::Xyz),
+ 11 => Ok(ColorPrimaries::Smpte431),
+ 12 => Ok(ColorPrimaries::Smpte432),
+ 22 => Ok(ColorPrimaries::Ebu3213),
+ _ => Err(format!("Invalid ColorPrimaries {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TransferCharacteristics {
+ Reserved0 = 0,
+ Bt709 = 1,
+ #[default]
+ Unspecified = 2,
+ Reserved3 = 3,
+ Bt470m = 4,
+ Bt470bg = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ Linear = 8,
+ Log100 = 9,
+ Log100Sqrt10 = 10,
+ Iec61966 = 11,
+ Bt1361 = 12,
+ Srgb = 13,
+ Bt202010Bit = 14,
+ Bt202012Bit = 15,
+ Smpte2084 = 16,
+ Smpte428 = 17,
+ Hlg = 18,
+}
+
+impl TryFrom for TransferCharacteristics {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(TransferCharacteristics::Reserved0),
+ 1 => Ok(TransferCharacteristics::Bt709),
+ 2 => Ok(TransferCharacteristics::Unspecified),
+ 3 => Ok(TransferCharacteristics::Reserved3),
+ 4 => Ok(TransferCharacteristics::Bt470m),
+ 5 => Ok(TransferCharacteristics::Bt470bg),
+ 6 => Ok(TransferCharacteristics::Bt601),
+ 7 => Ok(TransferCharacteristics::Smpte240),
+ 8 => Ok(TransferCharacteristics::Linear),
+ 9 => Ok(TransferCharacteristics::Log100),
+ 10 => Ok(TransferCharacteristics::Log100Sqrt10),
+ 11 => Ok(TransferCharacteristics::Iec61966),
+ 12 => Ok(TransferCharacteristics::Bt1361),
+ 13 => Ok(TransferCharacteristics::Srgb),
+ 14 => Ok(TransferCharacteristics::Bt202010Bit),
+ 15 => Ok(TransferCharacteristics::Bt202012Bit),
+ 16 => Ok(TransferCharacteristics::Smpte2084),
+ 17 => Ok(TransferCharacteristics::Smpte428),
+ 18 => Ok(TransferCharacteristics::Hlg),
+ _ => Err(format!("Invalid TransferCharacteristics {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum BitDepth {
+ #[default]
+ Depth8 = 0,
+ Depth10 = 1,
+ Depth12 = 2,
+}
+
+impl TryFrom for BitDepth {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(BitDepth::Depth8),
+ 1 => Ok(BitDepth::Depth10),
+ 2 => Ok(BitDepth::Depth12),
+ _ => Err(format!("Invalid BitDepth {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum MatrixCoefficients {
+ Identity = 0,
+ Bt709 = 1,
+ #[default]
+ Unspecified = 2,
+ Reserved3 = 3,
+ Fcc = 4,
+ Bt470bg = 5,
+ Bt601 = 6,
+ Smpte240 = 7,
+ Ycgco = 8,
+ Bt2020Ncl = 9,
+ Bt2020Cl = 10,
+ Smpte2085 = 11,
+ ChromaDerivedNcl = 12,
+ ChromaDerivedCl = 13,
+ Ictcp = 14,
+}
+
+impl TryFrom for MatrixCoefficients {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(MatrixCoefficients::Identity),
+ 1 => Ok(MatrixCoefficients::Bt709),
+ 2 => Ok(MatrixCoefficients::Unspecified),
+ 3 => Ok(MatrixCoefficients::Reserved3),
+ 4 => Ok(MatrixCoefficients::Fcc),
+ 5 => Ok(MatrixCoefficients::Bt470bg),
+ 6 => Ok(MatrixCoefficients::Bt601),
+ 7 => Ok(MatrixCoefficients::Smpte240),
+ 8 => Ok(MatrixCoefficients::Ycgco),
+ 9 => Ok(MatrixCoefficients::Bt2020Ncl),
+ 10 => Ok(MatrixCoefficients::Bt2020Cl),
+ 11 => Ok(MatrixCoefficients::Smpte2085),
+ 12 => Ok(MatrixCoefficients::ChromaDerivedNcl),
+ 13 => Ok(MatrixCoefficients::ChromaDerivedCl),
+ 14 => Ok(MatrixCoefficients::Ictcp),
+ _ => Err(format!("Invalid MatrixCoefficients {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ChromaSamplePosition {
+ #[default]
+ Unknown = 0,
+ Vertical = 1,
+ Colocated = 2,
+ Reserved = 3,
+}
+
+impl TryFrom for ChromaSamplePosition {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(ChromaSamplePosition::Unknown),
+ 1 => Ok(ChromaSamplePosition::Vertical),
+ 2 => Ok(ChromaSamplePosition::Colocated),
+ 3 => Ok(ChromaSamplePosition::Reserved),
+ _ => Err(format!("Invalid ChromaSamplePosition {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct ColorConfig {
+ /// Syntax elements which, together with seq_profile, determine the bit
+ /// depth.
+ pub high_bitdepth: bool,
+ /// Syntax elements which, together with seq_profile, determine the bit
+ /// depth.
+ pub twelve_bit: bool,
+ /// If set, indicates that the video does not contain U and V color planes.
+ /// If not set, indicates that the video contains Y, U, and V color planes.
+ pub mono_chrome: bool,
+ /// If set, specifies that color_primaries, transfer_characteristics, and
+ /// matrix_coefficients are present. If not set, specifies that
+ /// color_primaries, transfer_characteristics and matrix_coefficients are
+ /// not present.
+ pub color_description_present_flag: bool,
+ /// Defined by the “Color primaries” section of ISO/IEC 23091-4/ITU-T H.273.
+ pub color_primaries: ColorPrimaries,
+ /// Defined by the “Transfer characteristics” section of ISO/IEC
+ /// 23091-4/ITU-T H.273.
+ pub transfer_characteristics: TransferCharacteristics,
+ /// Defined by the “Matrix coefficients” section of ISO/IEC 23091-4/ITU-T
+ /// H.273.
+ pub matrix_coefficients: MatrixCoefficients,
+ /// Binary value that is associated with the VideoFullRangeFlag variable
+ /// specified in ISO/IEC 23091-4/ITU- T H.273. color range equal to 0 shall
+ /// be referred to as the studio swing representation and color range equal
+ /// to 1 shall be referred to as the full swing representation for all
+ /// intents relating to this specification.
+ pub color_range: bool,
+ /// Specify the chroma subsampling format
+ pub subsampling_x: bool,
+ /// Specify the chroma subsampling format
+ pub subsampling_y: bool,
+ /// Specifies the sample position for subsampled streams
+ pub chroma_sample_position: ChromaSamplePosition,
+ /// If set, indicates that the U and V planes may have separate delta
+ /// quantizer values. If not set, indicates that the U and V planes will
+ /// share the same delta quantizer value.
+ pub separate_uv_delta_q: bool,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct SequenceHeaderObu {
+ /// The OBU header from the OBU that generated this sequence.
+ pub obu_header: ObuHeader,
+ /// Specifies the features that can be used in the coded video sequence.
+ pub seq_profile: Profile,
+ /// If set, specifies that the coded video sequence contains only one coded
+ /// frame. If not set, specifies that the coded video sequence contains one
+ /// or more coded frames.
+ pub still_picture: bool,
+ /// Specifies that the syntax elements not needed by a still picture are
+ /// omitted.
+ pub reduced_still_picture_header: bool,
+ /// Specifies the number of bits minus 1 used for transmitting the frame
+ /// width syntax elements.
+ pub frame_width_bits_minus_1: u8,
+ /// Specifies the number of bits minus 1 used for transmitting the frame
+ /// height syntax elements.
+ pub frame_height_bits_minus_1: u8,
+ /// Specifies the maximum frame width minus 1 for the frames represented by
+ /// this sequence header.
+ pub max_frame_width_minus_1: u16,
+ /// Specifies the maximum frame height minus 1 for the frames represented by
+ /// this sequence header.
+ pub max_frame_height_minus_1: u16,
+ /// Specifies whether frame id numbers are present in the coded video
+ /// sequence.
+ pub frame_id_numbers_present_flag: bool,
+ /// Specifies the number of bits minus 2 used to encode delta_frame_id
+ /// syntax elements.
+ pub delta_frame_id_length_minus_2: u32,
+ /// Used to calculate the number of bits used to encode the frame_id syntax
+ /// element.
+ pub additional_frame_id_length_minus_1: u32,
+ /// When set, indicates that superblocks contain 128x128 luma samples. When
+ /// not set, it indicates that superblocks contain 64x64 luma samples. (The
+ /// number of contained chroma samples depends on subsampling_x and
+ /// subsampling_y.)
+ pub use_128x128_superblock: bool,
+ /// When set, specifies that the use_filter_intra syntax element may be
+ /// present. When not set, specifies that the use_filter_intra syntax
+ /// element will not be present.
+ pub enable_filter_intra: bool,
+ /// Specifies whether the intra edge filtering process should be enabled.
+ pub enable_intra_edge_filter: bool,
+ /// When set, specifies that the mode info for inter blocks may contain the
+ /// syntax element interintra. If not set, specifies that the syntax element
+ /// interintra will not be present.
+ pub enable_interintra_compound: bool,
+ /// When set, specifies that the mode info for inter blocks may contain the
+ /// syntax element compound_type. When not set, specifies that the syntax
+ /// element compound_type will not be present.
+ pub enable_masked_compound: bool,
+ /// When set, indicates that the allow_warped_motion syntax element may be
+ /// present. When not set, indicates that the allow_warped_motion syntax
+ /// element will not be present.
+ pub enable_warped_motion: bool,
+ /// When set, indicates that tools based on the values of order hints may be
+ /// used. When not set, indicates that tools based on order hints are
+ /// disabled.
+ pub enable_order_hint: bool,
+ /// When set, indicates that the inter prediction filter type may be
+ /// specified independently in the horizontal and vertical directions. If
+ /// the flag is not set, only one filter type may be specified, which is
+ /// then used in both directions.
+ pub enable_dual_filter: bool,
+ /// If set, indicates that the distance weights process may be used for
+ /// inter prediction.
+ pub enable_jnt_comp: bool,
+ /// If set, indicates that the use_ref_frame_mvs syntax element may be
+ /// present. If not set, indicates that the use_ref_frame_mvs syntax element
+ /// will not be present.
+ pub enable_ref_frame_mvs: bool,
+ /// If not set, indicates that the seq_force_screen_content_tools syntax
+ /// element will be present. If set, indicates that
+ /// seq_force_screen_content_tools should be set equal to
+ /// SELECT_SCREEN_CONTENT_TOOLS.
+ pub seq_choose_screen_content_tools: bool,
+ /// Equal to SELECT_SCREEN_CONTENT_TOOLS indicates that the
+ /// allow_screen_content_tools syntax element will be present in the frame
+ /// header. Otherwise, seq_force_screen_content_tools contains the value for
+ /// allow_screen_content_tools.
+ pub seq_force_screen_content_tools: u32,
+ /// If not set, indicates that the seq_force_integer_mv syntax element will
+ /// be present. If set, indicates that seq_force_integer_mv should be set
+ /// equal to SELECT_INTEGER_MV.
+ pub seq_choose_integer_mv: bool,
+ /// Equal to SELECT_INTEGER_MV indicates that the force_integer_mv syntax
+ /// element will be present in the frame header (providing
+ /// allow_screen_content_tools is equal to 1). Otherwise,
+ /// seq_force_integer_mv contains the value for force_integer_mv.
+ pub seq_force_integer_mv: u32,
+ /// Used to compute OrderHintBits.
+ pub order_hint_bits_minus_1: i32,
+ /// Specifies the number of bits used for the order_hint syntax element.
+ pub order_hint_bits: i32,
+ /// If set, specifies that the use_superres syntax element will be present
+ /// in the uncompressed header. If not set, specifies that the use_superres
+ /// syntax element will not be present (instead use_superres will be set to
+ /// 0 in the uncompressed header without being read).
+ pub enable_superres: bool,
+ /// If set, specifies that cdef filtering may be enabled. If not set,
+ /// specifies that cdef filtering is disabled.
+ pub enable_cdef: bool,
+ /// If set, specifies that loop restoration filtering may be enabled. If
+ /// not set, specifies that loop restoration filtering is disabled.
+ pub enable_restoration: bool,
+ /// Specifies whether film grain parameters are present in the coded video
+ /// sequence.
+ pub film_grain_params_present: bool,
+ /// Indicates the number of operating points minus 1 present in the coded
+ /// video sequence. An operating point specifies which spatial and temporal
+ /// layers should be decoded.
+ pub operating_points_cnt_minus_1: u32,
+ /// The set of operating points.
+ pub operating_points: [OperatingPoint; MAX_NUM_OPERATING_POINTS],
+ /// Specifies whether decoder model information is present in the coded
+ /// video sequence.
+ pub decoder_model_info_present_flag: bool,
+ /// The decoder model info.
+ pub decoder_model_info: DecoderModelInfo,
+ /// Specifies whether initial display delay information is present in the
+ /// coded video sequence.
+ pub initial_display_delay_present_flag: bool,
+ /// Specifies whether timing info is present in the coded video sequence.
+ pub timing_info_present_flag: bool,
+ /// The timing info.
+ pub timing_info: TimingInfo,
+ /// The color config.
+ pub color_config: ColorConfig,
+
+ /* CamelCase variables in the specification */
+ pub bit_depth: BitDepth,
+ pub num_planes: u32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct StreamInfo {
+ pub seq_header: Rc,
+ pub render_width: u32,
+ pub render_height: u32,
+}
+
+/// A TemporalDelimiterOBU
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct TemporalDelimiterObu {
+ pub obu_header: ObuHeader,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum InterpolationFilter {
+ #[default]
+ EightTap = 0,
+ EightTapSmooth = 1,
+ EightTapSharp = 2,
+ Bilinear = 3,
+ Switchable = 4,
+}
+
+impl TryFrom for InterpolationFilter {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(InterpolationFilter::EightTap),
+ 1 => Ok(InterpolationFilter::EightTapSmooth),
+ 2 => Ok(InterpolationFilter::EightTapSharp),
+ 3 => Ok(InterpolationFilter::Bilinear),
+ 4 => Ok(InterpolationFilter::Switchable),
+ _ => Err(format!("Invalid InterpolationFilter {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TxModes {
+ #[default]
+ Only4x4 = 0,
+ Largest = 1,
+ Select = 2,
+}
+
+impl TryFrom for TxModes {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(TxModes::Only4x4),
+ 1 => Ok(TxModes::Largest),
+ 2 => Ok(TxModes::Select),
+ _ => Err(format!("Invalid TxModes {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum FrameRestorationType {
+ #[default]
+ None = 0,
+ Wiener = 1,
+ Sgrproj = 2,
+ Switchable = 3,
+}
+
+impl TryFrom for FrameRestorationType {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(FrameRestorationType::None),
+ 1 => Ok(FrameRestorationType::Wiener),
+ 2 => Ok(FrameRestorationType::Sgrproj),
+ 3 => Ok(FrameRestorationType::Switchable),
+ _ => Err(format!("Invalid FrameRestorationType {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ReferenceFrameType {
+ #[default]
+ Intra = 0,
+ Last = 1,
+ Last2 = 2,
+ Last3 = 3,
+ Golden = 4,
+ BwdRef = 5,
+ AltRef2 = 6,
+ AltRef = 7,
+}
+
+impl TryFrom for ReferenceFrameType {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(ReferenceFrameType::Intra),
+ 1 => Ok(ReferenceFrameType::Last),
+ 2 => Ok(ReferenceFrameType::Last2),
+ 3 => Ok(ReferenceFrameType::Last3),
+ 4 => Ok(ReferenceFrameType::Golden),
+ 5 => Ok(ReferenceFrameType::BwdRef),
+ 6 => Ok(ReferenceFrameType::AltRef2),
+ 7 => Ok(ReferenceFrameType::AltRef),
+ _ => Err(format!("Invalid ReferenceFrameType {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum WarpModelType {
+ #[default]
+ Identity = 0,
+ Translation = 1,
+ RotZoom = 2,
+ Affine = 3,
+}
+
+impl TryFrom for WarpModelType {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(WarpModelType::Identity),
+ 1 => Ok(WarpModelType::Translation),
+ 2 => Ok(WarpModelType::RotZoom),
+ 3 => Ok(WarpModelType::Affine),
+ _ => Err(format!("Invalid WarpModelType {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum FrameType {
+ #[default]
+ KeyFrame = 0,
+ InterFrame = 1,
+ IntraOnlyFrame = 2,
+ SwitchFrame = 3,
+}
+
+impl TryFrom for FrameType {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(FrameType::KeyFrame),
+ 1 => Ok(FrameType::InterFrame),
+ 2 => Ok(FrameType::IntraOnlyFrame),
+ 3 => Ok(FrameType::SwitchFrame),
+ _ => Err(format!("Invalid FrameType {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub enum TxMode {
+ #[default]
+ Only4x4 = 0,
+ Largest = 1,
+ Select = 2,
+}
+
+impl TryFrom for TxMode {
+ type Error = String;
+
+ fn try_from(value: u32) -> Result {
+ match value {
+ 0 => Ok(TxMode::Only4x4),
+ 1 => Ok(TxMode::Largest),
+ 2 => Ok(TxMode::Select),
+ _ => Err(format!("Invalid TxMode {}", value)),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct FrameObu<'a> {
+ pub header: FrameHeaderObu,
+ pub tile_group: TileGroupObu<'a>,
+}
+
+/// A FrameHeaderOBU
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct FrameHeaderObu {
+ /// The original OBU header. This may be from a FrameOBU or a FrameHeaderOBU
+ /// directly.
+ pub obu_header: ObuHeader,
+ /// If set, indicates the frame indexed by frame_to_show_map_idx is to be
+ /// output; If not set, indicates that further processing is required.
+ pub show_existing_frame: bool,
+ /// Specifies the frame to be output. It is only available if
+ /// show_existing_frame is set.
+ pub frame_to_show_map_idx: u8,
+ /// Specifies the length of the frame_presentation_time syntax element, in
+ /// bits.
+ pub frame_presentation_time: u32,
+ /// Provides the frame id number for the frame to output.
+ pub display_frame_id: u32,
+ /// Specifies the type of the frame
+ pub frame_type: FrameType,
+ /// If set, specifies that this frame should be immediately output once
+ /// decoded. If not set specifies that this frame should not be
+ /// immediately output. (It may be output later if a later uncompressed
+ /// header uses show_existing_frame is set).
+ pub show_frame: bool,
+ /// When set, specifies that the frame may be output using the
+ /// show_existing_frame mechanism. When not set, specifies that this frame
+ /// will not be output using the show_existing_frame mechanism.
+ pub showable_frame: bool,
+ /// If set, indicates that error resilient mode is enabled;
+ /// error_resilient_mode equal to 0 indicates that error resilient mode is
+ /// disabled.
+ pub error_resilient_mode: bool,
+ /// Specifies whether the CDF update in the symbol decoding process should
+ /// be disabled.
+ pub disable_cdf_update: bool,
+ /// When set, indicates that intra blocks may use palette encoding; When not
+ /// set, indicates that palette encoding is never used.
+ pub allow_screen_content_tools: u32,
+ /// If set, specifies that motion vectors will always be integers. If not
+ /// set, specifies that motion vectors can contain fractional bits.
+ pub force_integer_mv: u32,
+ /// Specifies the frame id number for the current frame. Frame id numbers
+ /// are additional information that do not affect the decoding process, but
+ /// provide decoders with a way of detecting missing reference frames so
+ /// that appropriate action can be taken.
+ pub current_frame_id: u32,
+ /// If not set, specifies that the frame size is equal to the size in the
+ /// sequence header. If set, specifies that the frame size will either be
+ /// specified as the size of one of the reference frames, or computed from
+ /// the frame_width_minus_1 and frame_height_minus_1 syntax elements.
+ pub frame_size_override_flag: bool,
+ /// Specifies OrderHintBits least significant bits of the expected output
+ /// order for this frame.
+ pub order_hint: u32,
+ /// Specifies which reference frame contains the CDF values and other state
+ /// that should be loaded at the start of the frame.
+ pub primary_ref_frame: u32,
+ /// If set, specifies that buffer_removal_time is present. If not set,
+ /// specifies that buffer_removal_time is not present.
+ pub buffer_removal_time_present_flag: bool,
+ /// Specifies the frame removal time in units of DecCT clock ticks counted
+ /// from the removal time of the last random access point for operating
+ /// point opNum. buffer_removal_time is signaled as a fixed length unsigned
+ /// integer with a length in bits given by
+ /// buffer_removal_time_length_minus_1 + 1.
+ pub buffer_removal_time: Vec,
+ /// Contains a bitmask that specifies which reference frame slots will be
+ /// updated with the current frame after it is decoded.
+ pub refresh_frame_flags: u32,
+ /// Specifies the expected output order hint for each reference frame.
+ pub ref_order_hint: [u32; NUM_REF_FRAMES],
+ /// If set, indicates that intra block copy may be used in this frame. If
+ /// not set indicates that intra block copy is not allowed in this frame.
+ pub allow_intrabc: bool,
+ /// If set, indicates that only two reference frames are explicitly
+ /// signaled. If not set, indicates that all reference frames are explicitly
+ /// signaled.
+ pub frame_refs_short_signaling: bool,
+ /// Specifies the reference frame to use for LAST_FRAME.
+ pub last_frame_idx: u8,
+ /// Specifies the reference frame to use for GOLDEN_FRAME.
+ pub gold_frame_idx: u8,
+ /// Specifies which reference frames are used by inter frames
+ pub ref_frame_idx: [u8; REFS_PER_FRAME],
+ /// If not set, specifies that motion vectors are specified to quarter pel
+ /// precision; If set, specifies that motion vectors are specified to eighth
+ /// pel precision.
+ pub allow_high_precision_mv: bool,
+ /// If not set, specifies that only the SIMPLE motion mode will be used.
+ pub is_motion_mode_switchable: bool,
+ /// If set, specifies that motion vector information from a previous frame
+ /// can be used when decoding the current frame. If not set, specifies that
+ /// this information will not be used.
+ pub use_ref_frame_mvs: bool,
+ /// If set, indicates that the end of frame CDF update is disabled; If not
+ /// set, indicates that the end of frame CDF update is enabled.
+ pub disable_frame_end_update_cdf: bool,
+ /// If set, indicates that the syntax element motion_mode may be present.
+ /// If not set, indicates that the syntax element motion_mode will not be
+ /// present
+ pub allow_warped_motion: bool,
+ /// If set, specifies that the frame is restricted to a reduced subset of
+ /// the full set of transform types.
+ pub reduced_tx_set: bool,
+ /// If not set, means that the render width and height are inferred from the
+ /// frame width and height. If set, means that the render width and height
+ /// are explicitly coded.
+ pub render_and_frame_size_different: bool,
+ /// If not set, indicates that no upscaling is needed. If set, indicates
+ /// that upscaling is needed.
+ pub use_superres: bool,
+ /// If set indicates that the filter selection is signaled at the block
+ /// level; If not set, indicates that the filter selection is signaled at
+ /// the frame level.
+ pub is_filter_switchable: bool,
+ /// The interpolation filter parameters.
+ pub interpolation_filter: InterpolationFilter,
+ /// The loop filter parameters.
+ pub loop_filter_params: LoopFilterParams,
+ /// The quantization parameters.
+ pub quantization_params: QuantizationParams,
+ /// The segmentation parameters.
+ pub segmentation_params: SegmentationParams,
+ /// The tile info.
+ pub tile_info: TileInfo,
+ /// The CDEF parameters.
+ pub cdef_params: CdefParams,
+ /// The loop restoration parameters.
+ pub loop_restoration_params: LoopRestorationParams,
+ /// Used to compute TxMode.
+ pub tx_mode_select: u32,
+ /// If set specifies that the syntax element skip_mode will be present. If
+ /// not set, specifies that skip_mode will not be used for this frame.
+ pub skip_mode_present: bool,
+ /// If set, specifies that the mode info for inter blocks contains the
+ /// syntax element comp_mode that indicates whether to use single or
+ /// compound reference prediction. If not set, specifies that all inter
+ /// blocks will use single prediction.
+ pub reference_select: bool,
+ /// The global motion parameters.
+ pub global_motion_params: GlobalMotionParams,
+ /// The film grain parameters.
+ pub film_grain_params: FilmGrainParams,
+
+ /* CamelCase variables */
+ pub superres_denom: u32,
+ pub frame_is_intra: bool,
+ pub order_hints: [u32; NUM_REF_FRAMES],
+ pub ref_frame_sign_bias: [bool; NUM_REF_FRAMES],
+ pub coded_lossless: bool,
+ pub all_lossless: bool,
+ pub lossless_array: [bool; MAX_SEGMENTS],
+ pub seg_qm_level: [[u32; MAX_SEGMENTS]; 3],
+ pub upscaled_width: u32,
+ pub frame_width: u32,
+ pub frame_height: u32,
+ pub render_width: u32,
+ pub render_height: u32,
+ pub tx_mode: TxMode,
+ pub skip_mode_frame: [u32; 2],
+ pub mi_cols: u32,
+ pub mi_rows: u32,
+ pub header_bytes: usize,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct LoopFilterParams {
+ /// An array containing loop filter strength values. Different loop filter
+ /// strength values from the array are used depending on the image plane
+ /// being filtered, and the edge direction (vertical or horizontal) being
+ /// filtered.
+ pub loop_filter_level: [u8; 4],
+ /// Indicates the sharpness level. The loop_filter_level and
+ /// loop_filter_sharpness together determine when a block edge is filtered,
+ /// and by how much the filtering can change the sample values.
+ pub loop_filter_sharpness: u8,
+ /// If set, means that the filter level depends on the mode and reference
+ /// frame used to predict a block. If not set, means that the filter level
+ /// does not depend on the mode and reference frame.
+ pub loop_filter_delta_enabled: bool,
+ /// If set, means that additional syntax elements are present that specify
+ /// which mode and reference frame deltas are to be updated.
+ /// loop_filter_delta_update equal to 0 means that these syntax elements are
+ /// not present.
+ pub loop_filter_delta_update: bool,
+ /// Contains the adjustment needed for the filter level based on the chosen
+ /// reference frame. If this syntax element is not present, it maintains
+ /// its previous value.
+ pub loop_filter_ref_deltas: [i8; TOTAL_REFS_PER_FRAME],
+ /// Contains the adjustment needed for the filter level based on the chosen
+ /// mode. If this syntax element is not present in the, it maintains its
+ /// previous value.
+ pub loop_filter_mode_deltas: [i8; 2],
+ /// Specifies whether loop filter delta values are present.
+ pub delta_lf_present: bool,
+ /// Specifies the left shift which should be applied to decoded loop filter
+ /// delta values.
+ pub delta_lf_res: u8,
+ /// If set, specifies that separate loop filter deltas are sent for
+ /// horizontal luma edges, vertical luma edges, the U edges, and the V
+ /// edges. If not set, specifies that the same loop filter delta is used for
+ /// all edges.
+ pub delta_lf_multi: bool,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct QuantizationParams {
+ /// Indicates the base frame qindex. This is used for Y AC coefficients and
+ /// as the base value for the other quantizers.
+ pub base_q_idx: u32,
+ /// Indicates the base frame qindex. This is used for Y AC coefficients and
+ /// as the base value for the other quantizers.
+ pub diff_uv_delta: bool,
+ /// Specifies that the quantizer matrix will be used to compute quantizers.
+ pub using_qmatrix: bool,
+ /// Specifies the level in the quantizer matrix that should be used for luma
+ /// plane decoding.
+ pub qm_y: u32,
+ /// Specifies the level in the quantizer matrix that should be used for
+ /// chroma U plane decoding.
+ pub qm_u: u32,
+ /// Specifies the level in the quantizer matrix that should be used for
+ /// chroma V plane decoding.
+ pub qm_v: u32,
+ /// Specifies whether quantizer index delta values are present.
+ pub delta_q_present: bool,
+ /// Specifies the left shift which should be applied to decoded quantizer
+ /// index delta values.
+ pub delta_q_res: u32,
+ /// Same as DeltaQYDc
+ pub delta_q_y_dc: i32,
+ /// Same as DeltaQUDc
+ pub delta_q_u_dc: i32,
+ /// Same as DeltaQUAc
+ pub delta_q_u_ac: i32,
+ /// Same as DeltaQVDc
+ pub delta_q_v_dc: i32,
+ /// Same as DeltaQVAc
+ pub delta_q_v_ac: i32,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct SegmentationParams {
+ /// If set, indicates that this frame makes use of the segmentation tool; If
+ /// not set, indicates that the frame does not use segmentation.
+ pub segmentation_enabled: bool,
+ /// If set, indicates that the segmentation map are updated during the
+ /// decoding of this frame. If not set, means that the segmentation map from
+ /// the previous frame is used.
+ pub segmentation_update_map: bool,
+ /// If set, indicates that the updates to the segmentation map are coded
+ /// relative to the existing segmentation map. If not set, indicates that
+ /// the new segmentation map is coded without reference to the existing
+ /// segmentation map.
+ pub segmentation_temporal_update: bool,
+ /// If set, indicates that new parameters are about to be specified for each
+ /// segment. If not set, indicates that the segmentation parameters should
+ /// keep their existing values.
+ pub segmentation_update_data: bool,
+ /// If not set, indicates that the corresponding feature is unused and has
+ /// value equal to 0. If set, indicates that the feature value is coded.
+ pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS],
+ /// Specifies the feature data for a segment feature.
+ pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS],
+ /// Same as SegIdPreSkip
+ pub seg_id_pre_skip: bool,
+ /// Same as LastActiveSegId
+ pub last_active_seg_id: u8,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TileInfo {
+ /// If set, means that the tiles are uniformly spaced across the frame. (In
+ /// other words, all tiles are the same size except for the ones at the
+ /// right and bottom edge which can be smaller.) If not set, means that the
+ /// tile sizes are coded.
+ pub uniform_tile_spacing_flag: bool,
+ /// Used to compute TileColsLog2.
+ pub increment_tile_rows_log2: u32,
+ /// Specifies the width of a tile minus 1 in units of superblocks.
+ pub width_in_sbs_minus_1: [u32; MAX_TILE_COLS],
+ /// Specifies the height of a tile minus 1 in units of superblocks.
+ pub height_in_sbs_minus_1: [u32; MAX_TILE_ROWS],
+ /// Specifies which tile to use for the CDF update
+ pub context_update_tile_id: u32,
+ /// An array specifying the start column (in units of 4x4 luma samples) for
+ /// each tile across the image.
+ pub mi_col_starts: [u32; MAX_TILE_COLS + 1],
+ /// An array specifying the start row (in units of 4x4 luma samples) for
+ /// each tile down the image.
+ pub mi_row_starts: [u32; MAX_TILE_ROWS + 1],
+ /// Specifies the base 2 logarithm of the desired number of tiles down the
+ /// frame.
+ pub tile_cols_log2: u32,
+ /// Specifies the number of tiles across the frame.
+ pub tile_cols: u32,
+ /// Specifies the base 2 logarithm of the desired number of tiles down the
+ /// frame.
+ pub tile_rows_log2: u32,
+ /// Secifies the number of tiles down the frame
+ pub tile_rows: u32,
+ /// Specifies the number of bytes needed to code each tile size.
+ pub tile_size_bytes: u32,
+}
+
+impl Default for TileInfo {
+ fn default() -> Self {
+ Self {
+ uniform_tile_spacing_flag: Default::default(),
+ increment_tile_rows_log2: Default::default(),
+ width_in_sbs_minus_1: [0; MAX_TILE_COLS],
+ height_in_sbs_minus_1: [0; MAX_TILE_ROWS],
+ context_update_tile_id: Default::default(),
+ mi_col_starts: [0; MAX_TILE_COLS + 1],
+ mi_row_starts: [0; MAX_TILE_ROWS + 1],
+ tile_cols_log2: Default::default(),
+ tile_cols: Default::default(),
+ tile_rows_log2: Default::default(),
+ tile_rows: Default::default(),
+ tile_size_bytes: Default::default(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct CdefParams {
+ /// Controls the amount of damping in the deringing filter.
+ pub cdef_damping: u32,
+ /// Specifies the number of bits needed to specify which CDEF filter to
+ /// apply.
+ pub cdef_bits: u32,
+ /// Specify the strength of the primary filter.
+ pub cdef_y_pri_strength: [u32; CDEF_MAX],
+ /// Specify the strength of the secondary filter.
+ pub cdef_y_sec_strength: [u32; CDEF_MAX],
+ /// Specify the strength of the primary filter.
+ pub cdef_uv_pri_strength: [u32; CDEF_MAX],
+ /// Specify the strength of the secondary filter.
+ pub cdef_uv_sec_strength: [u32; CDEF_MAX],
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct LoopRestorationParams {
+ /// Specifies if the luma restoration size should be halved.
+ pub lr_unit_shift: u8,
+ /// Only present for 4:2:0 formats and specifies if the chroma size should
+ /// be half the luma size.
+ pub lr_uv_shift: u8,
+ /// Same as FrameRestorationType in the specification.
+ pub frame_restoration_type: [FrameRestorationType; MAX_NUM_PLANES],
+ /// Same as LoopRestorationSize in the specification.
+ pub loop_restoration_size: [u16; MAX_NUM_PLANES],
+ /// Same as UsesLr in the specification.
+ pub uses_lr: bool,
+ /// Same as UsesChromaLr in the specification.
+ pub uses_chroma_lr: bool,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct GlobalMotionParams {
+ /// Specifies whether global motion parameters are present for a particular
+ /// reference frame.
+ pub is_global: [bool; NUM_REF_FRAMES],
+ /// Specifies whether a particular reference frame uses rotation and zoom
+ /// global motion.
+ pub is_rot_zoom: [bool; NUM_REF_FRAMES],
+ /// Specifies whether a particular reference frame uses translation global
+ /// motion.
+ pub is_translation: [bool; NUM_REF_FRAMES],
+ /// gm_params\[ ref \]\[ j \] is set equal to SavedGmParams\[
+ /// frame_to_show_map_idx \]\[ ref \]\[ j \] for ref = LAST_FRAME..ALTREF_FRAME,
+ /// for j = 0..5.
+ pub gm_params: [[i32; 6]; NUM_REF_FRAMES],
+ /// Whether the parameters are valid (see warpValid and section 7.11.3.6)
+ pub warp_valid: [bool; NUM_REF_FRAMES],
+ /// Same as GmType.
+ pub gm_type: [WarpModelType; NUM_REF_FRAMES],
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct FilmGrainParams {
+ /// If set, specifies that film grain should be added to this frame. If not
+ /// set, specifies that film grain should not be added.
+ pub apply_grain: bool,
+ /// Specifies the starting value for the pseudo-random numbers used during
+ /// film grain synthesis.
+ pub grain_seed: u16,
+ /// If set means that a new set of parameters should be sent. If not set,
+ /// means that the previous set of parameters should be used.
+ pub update_grain: bool,
+ /// Indicates which reference frame contains the film grain parameters to be
+ /// used for this frame.
+ pub film_grain_params_ref_idx: u8,
+ /// Specifies the number of points for the piece-wise linear scaling
+ /// function of the luma component.
+ pub num_y_points: u8,
+ /// Represents the x (luma value) coordinate for the i-th point of the
+ /// piecewise linear scaling function for luma component. The values are
+ /// signaled on the scale of 0..255. (In case of 10 bit video, these values
+ /// correspond to luma values divided by 4. In case of 12 bit video, these
+ /// values correspond to luma values divided by 16.)
+ pub point_y_value: [u8; MAX_NUM_Y_POINTS],
+ /// Pepresents the scaling (output) value for the i-th point of the
+ /// piecewise linear scaling function for luma component.
+ pub point_y_scaling: [u8; MAX_NUM_Y_POINTS],
+ /// Specifies that the chroma scaling is inferred from the luma scaling.
+ pub chroma_scaling_from_luma: bool,
+ /// Specifies the number of points for the piece-wise linear scaling
+ /// function of the cb component.
+ pub num_cb_points: u8,
+ /// Represents the x coordinate for the i-th point of the piece-wise linear
+ /// scaling function for cb component. The values are signaled on the scale
+ /// of 0..255.
+ pub point_cb_value: [u8; MAX_NUM_CB_POINTS],
+ /// Represents the scaling (output) value for the i-th point of the
+ /// piecewise linear scaling function for cb component.
+ pub point_cb_scaling: [u8; MAX_NUM_CB_POINTS],
+ /// Specifies represents the number of points for the piece-wise linear
+ /// scaling function of the cr component.
+ pub num_cr_points: u8,
+ /// Represents the x coordinate for the i-th point of the piece-wise linear
+ /// scaling function for cr component. The values are signaled on the scale
+ /// of 0..255.
+ pub point_cr_value: [u8; MAX_NUM_CR_POINTS],
+ /// Represents the scaling (output) value for the i-th point of the
+ /// piecewise linear scaling function for cr component.
+ pub point_cr_scaling: [u8; MAX_NUM_CR_POINTS],
+ /// Represents the shift – 8 applied to the values of the chroma component.
+ /// The grain_scaling_minus_8 can take values of 0..3 and determines the
+ /// range and quantization step of the standard deviation of film grain.
+ pub grain_scaling_minus_8: u8,
+ /// Specifies the number of auto-regressive coefficients for luma and chroma.
+ pub ar_coeff_lag: u32,
+ /// Specifies auto-regressive coefficients used for the Y plane.
+ pub ar_coeffs_y_plus_128: [u8; MAX_NUM_POS_LUMA],
+ /// Specifies auto-regressive coefficients used for the U plane.
+ pub ar_coeffs_cb_plus_128: [u8; MAX_NUM_POS_LUMA],
+ /// Specifies auto-regressive coefficients used for the V plane.
+ pub ar_coeffs_cr_plus_128: [u8; MAX_NUM_POS_LUMA],
+ /// Specifies the range of the auto-regressive coefficients. Values of 0, 1,
+ /// 2, and 3 correspond to the ranges for auto-regressive coefficients of
+ /// [-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, 0.25) respectively.
+ pub ar_coeff_shift_minus_6: u8,
+ /// Specifies how much the Gaussian random numbers should be scaled down
+ /// during the grain synthesis process.
+ pub grain_scale_shift: u8,
+ /// Represents a multiplier for the cb component used in derivation of the
+ /// input index to the cb component scaling function.
+ pub cb_mult: u8,
+ /// Represents a multiplier for the average luma component used in
+ /// derivation of the input index to the cb component scaling function.
+ pub cb_luma_mult: u8,
+ /// Represents an offset used in derivation of the input index to the cb
+ /// component scaling function.
+ pub cb_offset: u16,
+ /// Represents a multiplier for the cr component used in derivation of the
+ /// input index to the cr component scaling function.
+ pub cr_mult: u8,
+ /// Represents a multiplier for the average luma component used in
+ /// derivation of the input index to the cr component scaling function.
+ pub cr_luma_mult: u8,
+ /// Represents an offset used in derivation of the input index to the cr
+ /// component scaling function.
+ pub cr_offset: u16,
+ /// If set, indicates that the overlap between film grain blocks shall be
+ /// applied. If not set, indicates that the overlap between film grain
+ /// blocks shall not be applied.
+ pub overlap_flag: bool,
+ /// If set, indicates that clipping to the restricted (studio) range shall
+ /// be applied to the sample values after adding the film grain (see the
+ /// semantics for color_range for an explanation of studio swing). If not
+ /// set, indicates that clipping to the full range shall be applied to the
+ /// sample values after adding the film grain.
+ pub clip_to_restricted_range: bool,
+}
+
+/// Keeps track of the state of the reference frames in the parser. All
+/// variables are CamelCase.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+struct ReferenceFrameInfo {
+ /// An array which is indexed by a reference picture slot number. A value of
+ /// true in the array signifies that the corresponding reference picture
+ /// slot is valid for use as a reference picture, while a value of false
+ /// signifies that the corresponding reference picture slot is not valid for
+ /// use as a reference picture.
+ ref_valid: bool,
+ /// Specifies the frame id for each reference frame.
+ ref_frame_id: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_upscaled_width: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_frame_width: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_frame_height: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_render_width: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_render_height: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_mi_cols: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_mi_rows: u32,
+ /// See 7.20 Reference Frame Update Process.
+ ref_frame_type: FrameType,
+ /// See 7.20 Reference Frame Update Process.
+ ref_subsampling_x: bool,
+ /// See 7.20 Reference Frame Update Process.
+ ref_subsampling_y: bool,
+ /// See 7.20 Reference Frame Update Process.
+ ref_bit_depth: BitDepth,
+ /// See 7.20 Reference Frame Update Process.
+ ref_order_hint: u32,
+ /// The saved segmentation parameters.
+ segmentation_params: SegmentationParams,
+ /// The saved global motion parameters.
+ global_motion_params: GlobalMotionParams,
+ /// The saved loop filter parameters.
+ loop_filter_params: LoopFilterParams,
+ /// The saved film grain parameters.
+ film_grain_params: FilmGrainParams,
+ /// The saved tile info parameters.
+ tile_info: TileInfo,
+ display_frame_id: u32,
+ showable_frame: bool,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AnnexBState {
+ pub temporal_unit_size: u32,
+ pub frame_unit_size: u32,
+ pub temporal_unit_consumed: u32,
+ pub frame_unit_consumed: u32,
+}
+
+#[derive(Clone, Debug)]
+enum StreamFormat {
+ LowOverhead,
+ AnnexB(AnnexBState),
+}
+
+#[derive(Debug)]
+pub struct Parser {
+ stream_format: StreamFormat,
+ operating_point: u32,
+ /// Same as SeenFrameHeader in the specification
+ seen_frame_header: bool,
+ operating_point_idc: u16,
+ should_probe_for_annexb: bool,
+ is_first_frame: bool,
+ ref_info: [ReferenceFrameInfo; NUM_REF_FRAMES],
+
+ /* CamelCase variables */
+ mi_cols: u32,
+ mi_rows: u32,
+ prev_frame_id: u32,
+ current_frame_id: u32,
+ mi_col_starts: [u32; MAX_TILE_COLS + 1],
+ mi_row_starts: [u32; MAX_TILE_ROWS + 1],
+ tile_cols_log2: u32,
+ tile_cols: u32,
+ tile_rows_log2: u32,
+ tile_rows: u32,
+ tile_size_bytes: u32,
+
+ /// We keep this to implement frame_header_copy() in the specification, and to fill in
+ /// StreamInfo render_width and render_height.
+ pub last_frame_header: Option,
+ /// The last SequenceHeaderObu parsed.
+ pub sequence_header: Option>,
+}
+
+impl Parser {
+ /// Probes the input data for the Annex B format. Anything other than
+ /// Ok(true) refers to data in "low-overhead" format instead, as we are trying to parse
+ fn annexb_probe(data: &[u8]) -> Result {
+ let mut r = Reader::new(data);
+ let mut seen_sequence = false;
+ let mut seen_frame = false;
+
+ // Try reading the first TU and frame unit size
+ let temporal_unit_size = r.read_leb128()?;
+ if temporal_unit_size == 0 {
+ return Ok(false);
+ }
+
+ let frame_unit_size = r.read_leb128()?;
+ if frame_unit_size == 0 || frame_unit_size > temporal_unit_size {
+ return Ok(false);
+ }
+
+ let obu_length = r.read_leb128()?;
+ if obu_length == 0 || obu_length > frame_unit_size {
+ return Ok(false);
+ }
+
+ // The first OBU in the first frame_unit of each temporal_unit must
+ // be a temporal delimiter OBU (and this is the only place temporal
+ // delimiter OBUs can appear)
+ let header = Self::parse_obu_header(&mut r.clone())?;
+ if !matches!(header.obu_type, ObuType::TemporalDelimiter) {
+ return Ok(false);
+ }
+
+ // Try identifying a sequence and a frame.
+ r.0.skip_bits(obu_length as usize * 8)?;
+ let mut num_bytes_read = 0;
+
+ loop {
+ let obu_length = r.read_leb128()?;
+ let mut obu_reader = r.clone();
+
+ r.0.skip_bits(obu_length as usize * 8)?;
+ num_bytes_read += obu_length;
+
+ if !seen_sequence {
+ let header = Self::parse_obu_header(&mut obu_reader)?;
+ seen_sequence = matches!(header.obu_type, ObuType::SequenceHeader);
+ }
+
+ if !seen_frame {
+ let header = Self::parse_obu_header(&mut obu_reader)?;
+ seen_frame = matches!(header.obu_type, ObuType::Frame | ObuType::FrameHeader);
+ }
+
+ if seen_sequence && seen_frame {
+ // OK, enough evidence of Annex B format.
+ return Ok(true);
+ }
+
+ if num_bytes_read >= frame_unit_size {
+ // We read what we've identified as the first frame and yet no
+ // sequence and no actual frames were found.
+ return Ok(false);
+ }
+ }
+ }
+
+ fn compute_image_size(&mut self, fh: &mut FrameHeaderObu) {
+ fh.mi_cols = 2 * ((fh.frame_width + 7) >> 3);
+ fh.mi_rows = 2 * ((fh.frame_height + 7) >> 3);
+ self.mi_cols = fh.mi_cols;
+ self.mi_rows = fh.mi_rows;
+ }
+
+ // 5.9.8
+ fn parse_superres_params(
+ fh: &mut FrameHeaderObu,
+ r: &mut Reader,
+ seq: &SequenceHeaderObu,
+ ) -> Result<(), String> {
+ if seq.enable_superres {
+ fh.use_superres = r.0.read_bit()?;
+ } else {
+ fh.use_superres = false;
+ }
+
+ if fh.use_superres {
+ fh.superres_denom =
+ r.0.read_bits::(SUPERRES_DENOM_BITS)? + SUPERRES_DENOM_MIN as u32;
+ } else {
+ fh.superres_denom = SUPERRES_NUM as u32;
+ }
+
+ fh.upscaled_width = fh.frame_width;
+ fh.frame_width =
+ (fh.upscaled_width * SUPERRES_NUM as u32 + (fh.superres_denom / 2)) / fh.superres_denom;
+
+ Ok(())
+ }
+
+ // 7.8 verbatim.
+ fn set_frame_refs(
+ &self,
+ fh: &mut FrameHeaderObu,
+ ref_order_hint: &[u32; NUM_REF_FRAMES],
+ ) -> Result<(), String> {
+ let seq = self.sequence()?;
+ let mut ref_frame_idx = [-1i32; REFS_PER_FRAME];
+
+ ref_frame_idx[0] = fh.last_frame_idx.into();
+ ref_frame_idx[ReferenceFrameType::Golden as usize - ReferenceFrameType::Last as usize] =
+ fh.gold_frame_idx.into();
+
+ let mut used_frame = [false; NUM_REF_FRAMES];
+ used_frame[fh.last_frame_idx as usize] = true;
+ used_frame[fh.gold_frame_idx as usize] = true;
+
+ let cur_frame_hint = 1 << (seq.order_hint_bits - 1);
+ let mut shifted_order_hints = [0; NUM_REF_FRAMES];
+ for i in 0..NUM_REF_FRAMES {
+ shifted_order_hints[i] = cur_frame_hint
+ + helpers::get_relative_dist(
+ seq.enable_order_hint,
+ seq.order_hint_bits,
+ ref_order_hint[i].try_into().unwrap(),
+ fh.order_hint.try_into().unwrap(),
+ );
+ }
+
+ let mut latest_order_hint = shifted_order_hints[fh.last_frame_idx as usize];
+ if latest_order_hint >= cur_frame_hint {
+ return Err("It is a requirement of bitstream conformance that last_order_hint < cur_frame_hint".into());
+ }
+
+ let mut earliest_order_hint = shifted_order_hints[fh.gold_frame_idx as usize];
+ if earliest_order_hint >= cur_frame_hint {
+ return Err("It is a requirement of bitstream conformance that gold_order_hint < cur_frame_hint".into());
+ }
+
+ let ref_ = helpers::find_latest_backward(
+ &shifted_order_hints,
+ &used_frame,
+ cur_frame_hint,
+ &mut latest_order_hint,
+ );
+
+ if ref_ >= 0 {
+ ref_frame_idx
+ [ReferenceFrameType::AltRef as usize - ReferenceFrameType::Last as usize] = ref_;
+ used_frame[ref_ as usize] = true;
+ }
+
+ let ref_ = helpers::find_earliest_backward(
+ &shifted_order_hints,
+ &used_frame,
+ cur_frame_hint,
+ &mut earliest_order_hint,
+ );
+
+ if ref_ >= 0 {
+ ref_frame_idx
+ [ReferenceFrameType::BwdRef as usize - ReferenceFrameType::Last as usize] = ref_;
+ used_frame[ref_ as usize] = true;
+ }
+
+ let ref_ = helpers::find_earliest_backward(
+ &shifted_order_hints,
+ &used_frame,
+ cur_frame_hint,
+ &mut earliest_order_hint,
+ );
+
+ if ref_ >= 0 {
+ ref_frame_idx
+ [ReferenceFrameType::AltRef2 as usize - ReferenceFrameType::Last as usize] = ref_;
+ used_frame[ref_ as usize] = true;
+ }
+
+ const REF_FRAME_LIST: [usize; 5] = [
+ ReferenceFrameType::Last2 as usize - ReferenceFrameType::Last as usize,
+ ReferenceFrameType::Last3 as usize - ReferenceFrameType::Last as usize,
+ ReferenceFrameType::BwdRef as usize - ReferenceFrameType::Last as usize,
+ ReferenceFrameType::AltRef2 as usize - ReferenceFrameType::Last as usize,
+ ReferenceFrameType::AltRef as usize - ReferenceFrameType::Last as usize,
+ ];
+
+ #[allow(clippy::needless_range_loop)]
+ for i in 0..REFS_PER_FRAME - 2 {
+ let ref_frame = REF_FRAME_LIST[i];
+
+ if ref_frame_idx[ref_frame] < 0 {
+ let ref_ = helpers::find_latest_forward(
+ &shifted_order_hints,
+ &used_frame,
+ cur_frame_hint,
+ &mut latest_order_hint,
+ );
+
+ if ref_ >= 0 {
+ ref_frame_idx[ref_frame] = ref_;
+ used_frame[ref_ as usize] = true;
+ }
+ }
+ }
+
+ let mut ref_ = 0;
+ earliest_order_hint = shifted_order_hints[0];
+ #[allow(clippy::needless_range_loop)]
+ for i in 1..NUM_REF_FRAMES {
+ let hint = shifted_order_hints[i];
+ if hint < earliest_order_hint {
+ ref_ = i as u8;
+ earliest_order_hint = hint;
+ }
+ }
+
+ fh.ref_frame_idx
+ .iter_mut()
+ .zip(ref_frame_idx.iter().copied())
+ .for_each(|(dest, src)| *dest = if src < 0 { ref_ } else { src as u8 });
+
+ Ok(())
+ }
+
+ // 5.9.5.
+ fn parse_frame_size(&mut self, fh: &mut FrameHeaderObu, r: &mut Reader) -> Result<(), String> {
+ let seq = self.sequence()?;
+ if fh.frame_size_override_flag {
+ let n = seq.frame_width_bits_minus_1 + 1;
+ fh.frame_width = r.0.read_bits::(n as usize)? + 1;
+
+ let n = seq.frame_height_bits_minus_1 + 1;
+ fh.frame_height = r.0.read_bits::(n as usize)? + 1;
+ } else {
+ fh.frame_width = seq.max_frame_width_minus_1 as u32 + 1;
+ fh.frame_height = seq.max_frame_height_minus_1 as u32 + 1;
+ }
+
+ Self::parse_superres_params(fh, r, seq)?;
+ self.compute_image_size(fh);
+
+ Ok(())
+ }
+
+ fn parse_render_size(fh: &mut FrameHeaderObu, r: &mut Reader) -> Result<(), String> {
+ fh.render_and_frame_size_different = r.0.read_bit()?;
+ if fh.render_and_frame_size_different {
+ fh.render_width = r.0.read_bits::(16)? + 1;
+ fh.render_height = r.0.read_bits::(16)? + 1;
+ } else {
+ fh.render_width = fh.upscaled_width;
+ fh.render_height = fh.frame_height;
+ }
+ Ok(())
+ }
+
+ fn frame_size_with_refs(
+ &mut self,
+ fh: &mut FrameHeaderObu,
+ r: &mut Reader,
+ ) -> Result<(), String> {
+ let mut found_ref = false;
+ let seq = self.sequence()?;
+
+ for i in 0..REFS_PER_FRAME {
+ found_ref = r.0.read_bit()?;
+
+ if found_ref {
+ let rf = &self.ref_info[fh.ref_frame_idx[i] as usize];
+ fh.upscaled_width = rf.ref_upscaled_width;
+ fh.frame_width = fh.upscaled_width;
+ fh.frame_height = rf.ref_frame_height;
+ fh.render_width = rf.ref_render_width;
+ fh.render_height = rf.ref_render_height;
+ break;
+ }
+ }
+
+ if !found_ref {
+ self.parse_frame_size(fh, r)?;
+ Self::parse_render_size(fh, r)?;
+ } else {
+ Self::parse_superres_params(fh, r, seq)?;
+ self.compute_image_size(fh);
+ }
+
+ Ok(())
+ }
+
+ /// Skip the padding bits, ensuring that they actually make sense.
+ fn skip_and_check_trailing_bits(r: &mut Reader, obu: &Obu) -> Result<(), String> {
+ // We can't have that in parse_obu as per the spec, because the reader
+ // is not initialized on our design at that point, so move the check to
+ // inside this function.
+ if obu.data.len() == 0
+ || matches!(
+ obu.header.obu_type,
+ ObuType::TileList | ObuType::TileGroup | ObuType::Frame
+ )
+ {
+ return Ok(());
+ }
+ let num_trailing = obu.as_ref().len() as u64 * 8 - r.0.position();
+ r.read_trailing_bits(num_trailing)?;
+ Ok(())
+ }
+
+ fn parse_obu_header(r: &mut Reader) -> Result {
+ let _obu_forbidden_bit = r.0.read_bit()?;
+
+ let mut header = ObuHeader {
+ obu_type: ObuType::try_from(r.0.read_bits::(4)?)?,
+ extension_flag: r.0.read_bit()?,
+ has_size_field: r.0.read_bit()?,
+ temporal_id: Default::default(),
+ spatial_id: Default::default(),
+ };
+
+ let obu_reserved_1bit = r.0.read_bit()?;
+ assert!(!obu_reserved_1bit); // Must be set to zero as per spec.
+
+ if header.extension_flag {
+ header.temporal_id = r.0.read_bits::(3)?;
+ header.spatial_id = r.0.read_bits::(2)?;
+ let _ = r.0.read_bits::(3)?;
+ }
+
+ Ok(header)
+ }
+
+ /// Parses one OBU from `data`, which can be in Annex B or low-overhead
+ /// format.
+ ///
+ /// `None` may eventually be returned if the OBU is to be dropped.
+ pub fn read_obu<'a>(&mut self, data: &'a [u8]) -> Result, String> {
+ if data.is_empty() {
+ return Err("Empty data".into());
+ }
+
+ let mut reader = Reader::new(data);
+
+ if self.should_probe_for_annexb {
+ // Try probing for Annex B data.
+ self.stream_format = if matches!(Self::annexb_probe(data), Ok(true)) {
+ log::debug!("Parsing an Annex B stream");
+ StreamFormat::AnnexB(AnnexBState::default())
+ } else {
+ log::debug!("Parsing a low-overhead stream");
+ StreamFormat::LowOverhead
+ };
+
+ self.should_probe_for_annexb = false;
+ }
+
+ let obu_length: usize = if let StreamFormat::AnnexB(annexb_state) = &mut self.stream_format
+ {
+ // Read the length to skip to the start of the open_bitstream_unit()
+ // syntax element.
+ let obu_length = reader.current_annexb_obu_length(annexb_state)?;
+ match obu_length {
+ Some(length) => length,
+ None => return Ok(ObuAction::Drop(reader.consumed(0))),
+ }
+ } else {
+ 0
+ };
+
+ let start_pos = reader.consumed(0);
+
+ // Both "low-overhead" and Annex B are now at the same point, i.e.: a
+ // open_bitstream_unit() follows.
+ let header = Self::parse_obu_header(&mut reader)?;
+ if matches!(self.stream_format, StreamFormat::LowOverhead) {
+ assert!(header.has_size_field);
+ }
+
+ let obu_size: usize = if header.has_size_field {
+ reader.read_leb128()? as usize
+ } else {
+ /* trap any bugs when computing the final length */
+ obu_length
+ .checked_sub(1)
+ .ok_or::("obu_length must be greater than 0".into())?
+ .checked_sub(usize::from(header.extension_flag))
+ .ok_or::("obu_length too short".into())?
+ };
+
+ let consumed = reader.consumed(start_pos);
+
+ if let StreamFormat::AnnexB(annexb_state) = &mut self.stream_format {
+ annexb_state.temporal_unit_consumed += consumed;
+ annexb_state.frame_unit_consumed += consumed;
+
+ annexb_state.temporal_unit_consumed += u32::try_from(obu_size).unwrap();
+ annexb_state.frame_unit_consumed += u32::try_from(obu_size).unwrap();
+ }
+
+ assert!(reader.0.position() % 8 == 0);
+ let start_offset: usize = (reader.0.position() / 8).try_into().unwrap();
+
+ log::debug!(
+ "Identified OBU type {:?}, data size: {}, obu_size: {}",
+ header.obu_type,
+ start_offset + obu_size,
+ obu_size
+ );
+
+ if header.obu_type != ObuType::SequenceHeader
+ && header.obu_type != ObuType::TemporalDelimiter
+ && self.operating_point_idc != 0
+ && header.extension_flag
+ {
+ let in_temporal_layer = ((self.operating_point_idc >> header.temporal_id) & 1) != 0;
+ let in_spatial_layer = ((self.operating_point_idc >> (header.spatial_id + 8)) & 1) != 0;
+ if !in_temporal_layer || !in_spatial_layer {
+ log::debug!("Dropping obu as per drop_obu() in the specification",);
+ return Ok(ObuAction::Drop(reader.consumed(0)));
+ }
+ }
+
+ Ok(ObuAction::Process(Obu {
+ header,
+ data: Cow::from(&data[start_offset..start_offset + obu_size]),
+ bytes_used: start_offset + obu_size,
+ }))
+ }
+
+ fn parse_color_config(s: &mut SequenceHeaderObu, r: &mut Reader) -> Result<(), String> {
+ let cc = &mut s.color_config;
+
+ cc.high_bitdepth = r.0.read_bit()?;
+ if s.seq_profile as u32 == 2 && cc.high_bitdepth {
+ cc.twelve_bit = r.0.read_bit()?;
+ if cc.twelve_bit {
+ s.bit_depth = BitDepth::Depth12;
+ } else {
+ s.bit_depth = BitDepth::Depth10;
+ }
+ } else if s.seq_profile as u32 <= 2 {
+ s.bit_depth = if cc.high_bitdepth {
+ BitDepth::Depth10
+ } else {
+ BitDepth::Depth8
+ };
+ }
+
+ if s.seq_profile as u32 == 1 {
+ cc.mono_chrome = false;
+ } else {
+ cc.mono_chrome = r.0.read_bit()?;
+ }
+
+ if cc.mono_chrome {
+ s.num_planes = 1;
+ } else {
+ s.num_planes = 3;
+ }
+
+ cc.color_description_present_flag = r.0.read_bit()?;
+ if cc.color_description_present_flag {
+ cc.color_primaries = ColorPrimaries::try_from(r.0.read_bits::(8)?)?;
+ cc.transfer_characteristics =
+ TransferCharacteristics::try_from(r.0.read_bits::(8)?)?;
+ cc.matrix_coefficients = MatrixCoefficients::try_from(r.0.read_bits::(8)?)?;
+ } else {
+ cc.color_primaries = ColorPrimaries::Unspecified;
+ cc.transfer_characteristics = TransferCharacteristics::Unspecified;
+ cc.matrix_coefficients = MatrixCoefficients::Unspecified;
+ }
+
+ if cc.mono_chrome {
+ cc.color_range = r.0.read_bit()?;
+ cc.subsampling_x = true;
+ cc.subsampling_y = true;
+ cc.chroma_sample_position = ChromaSamplePosition::Unknown;
+ cc.separate_uv_delta_q = false;
+ return Ok(());
+ } else if matches!(cc.color_primaries, ColorPrimaries::Bt709)
+ && matches!(cc.transfer_characteristics, TransferCharacteristics::Srgb)
+ && matches!(cc.matrix_coefficients, MatrixCoefficients::Identity)
+ {
+ cc.color_range = true;
+ cc.subsampling_x = false;
+ cc.subsampling_y = false;
+ } else {
+ cc.color_range = r.0.read_bit()?;
+ if s.seq_profile as u32 == 0 {
+ cc.subsampling_x = true;
+ cc.subsampling_y = true;
+ } else if s.seq_profile as u32 == 1 {
+ cc.subsampling_x = false;
+ cc.subsampling_y = false;
+ } else if matches!(s.bit_depth, BitDepth::Depth12) {
+ cc.subsampling_x = r.0.read_bit()?;
+ if cc.subsampling_x {
+ cc.subsampling_y = r.0.read_bit()?;
+ } else {
+ cc.subsampling_y = false;
+ }
+ } else {
+ cc.subsampling_x = true;
+ cc.subsampling_y = false;
+ }
+
+ if cc.subsampling_x && cc.subsampling_y {
+ cc.chroma_sample_position =
+ ChromaSamplePosition::try_from(r.0.read_bits::(2)?)?;
+ }
+ }
+
+ cc.separate_uv_delta_q = r.0.read_bit()?;
+
+ Ok(())
+ }
+
+ fn parse_operating_parameters_info(
+ opi: &mut OperatingPoint,
+ r: &mut Reader,
+ buffer_delay_length_minus_1: u8,
+ ) -> Result<(), String> {
+ let n = buffer_delay_length_minus_1 + 1;
+ opi.decoder_buffer_delay = r.0.read_bits::(n as usize)?;
+ opi.encoder_buffer_delay = r.0.read_bits::(n as usize)?;
+ opi.low_delay_mode_flag = r.0.read_bit()?;
+ Ok(())
+ }
+
+ fn parse_decoder_model_info(dmi: &mut DecoderModelInfo, r: &mut Reader) -> Result<(), String> {
+ dmi.buffer_delay_length_minus_1 = r.0.read_bits::(5)? as u8;
+ dmi.num_units_in_decoding_tick = r.0.read_bits::(32)?;
+ dmi.buffer_removal_time_length_minus_1 = r.0.read_bits::(5)? as u8;
+ dmi.frame_presentation_time_length_minus_1 = r.0.read_bits::(5)?;
+ Ok(())
+ }
+
+ fn parse_timing_info(ti: &mut TimingInfo, r: &mut Reader) -> Result<(), String> {
+ ti.num_units_in_display_tick = r.0.read_bits::(32)?;
+ ti.time_scale = r.0.read_bits::(32)?;
+ ti.equal_picture_interval = r.0.read_bit()?;
+ if ti.equal_picture_interval {
+ ti.num_ticks_per_picture_minus_1 = r.read_uvlc()?;
+ }
+ Ok(())
+ }
+
+ /// Selects an operating point. Only call this after the Sequence OBU for
+ /// which the operating point should apply has been parsed.
+ pub fn choose_operating_point(&mut self, operating_point: u32) -> Result<(), String> {
+ if operating_point > self.sequence()?.operating_points_cnt_minus_1 {
+ return Err(format!(
+ "Invalid operating point {} (max {})",
+ operating_point,
+ self.sequence()?.operating_points_cnt_minus_1
+ ));
+ }
+ self.operating_point = operating_point;
+ self.operating_point_idc = self.sequence()?.operating_points[operating_point as usize].idc;
+ Ok(())
+ }
+
+ fn parse_temporal_delimiter_obu(&mut self) -> Result<(), String> {
+ self.seen_frame_header = false;
+ Ok(())
+ }
+
+ fn parse_sequence_header_obu(&mut self, obu: &Obu) -> Result, String> {
+ let mut s = SequenceHeaderObu {
+ obu_header: obu.header.clone(),
+ ..Default::default()
+ };
+
+ let mut r = Reader::new(obu.as_ref());
+ let profile = r.0.read_bits::(3)?;
+
+ s.seq_profile = Profile::try_from(profile)?;
+ s.still_picture = r.0.read_bit()?;
+ s.reduced_still_picture_header = r.0.read_bit()?;
+
+ if s.reduced_still_picture_header {
+ /* Default::default() already ensures a lot of this, but lets go verbatim */
+ s.timing_info_present_flag = false;
+ s.decoder_model_info_present_flag = false;
+ s.initial_display_delay_present_flag = false;
+ s.operating_points_cnt_minus_1 = 0;
+ s.operating_points[0].idc = 0;
+ s.operating_points[0].seq_level_idx = r.0.read_bits::(5)? as u8;
+ s.operating_points[0].seq_tier = 0;
+ s.operating_points[0].decoder_model_present_for_this_op = false;
+ s.operating_points[0].initial_display_delay_present_for_this_op = false;
+ } else {
+ s.timing_info_present_flag = r.0.read_bit()?;
+ if s.timing_info_present_flag {
+ Self::parse_timing_info(&mut s.timing_info, &mut r)?;
+ s.decoder_model_info_present_flag = r.0.read_bit()?;
+ if s.decoder_model_info_present_flag {
+ Self::parse_decoder_model_info(&mut s.decoder_model_info, &mut r)?;
+ }
+ } else {
+ s.decoder_model_info_present_flag = false;
+ }
+
+ s.initial_display_delay_present_flag = r.0.read_bit()?;
+ s.operating_points_cnt_minus_1 = r.0.read_bits::(5)?;
+ if s.operating_points_cnt_minus_1 > MAX_NUM_OPERATING_POINTS as u32 {
+ return Err(format!(
+ "Invalid operating_points_cnt_minus_1 {}",
+ s.operating_points_cnt_minus_1
+ ));
+ }
+
+ for i in 0..=s.operating_points_cnt_minus_1 as usize {
+ s.operating_points[i].idc = r.0.read_bits::(12)? as u16;
+ s.operating_points[i].seq_level_idx = r.0.read_bits::(5)? as u8;
+ if s.operating_points[i].seq_level_idx > 7 {
+ s.operating_points[i].seq_tier = r.0.read_bit()? as u8;
+ } else {
+ s.operating_points[i].seq_tier = 0;
+ }
+ if s.decoder_model_info_present_flag {
+ s.operating_points[i].decoder_model_present_for_this_op = r.0.read_bit()?;
+ if s.operating_points[i].decoder_model_present_for_this_op {
+ let buffer_delay_length_minus_1 =
+ s.decoder_model_info.buffer_delay_length_minus_1;
+ Self::parse_operating_parameters_info(
+ &mut s.operating_points[i],
+ &mut r,
+ buffer_delay_length_minus_1,
+ )?;
+ }
+ } else {
+ s.operating_points[i].decoder_model_present_for_this_op = false;
+ }
+
+ if s.initial_display_delay_present_flag {
+ s.operating_points[i].initial_display_delay_present_for_this_op =
+ r.0.read_bit()?;
+ if s.operating_points[i].initial_display_delay_present_for_this_op {
+ s.operating_points[i].initial_display_delay_minus_1 =
+ r.0.read_bits::(4)?;
+ }
+ }
+ }
+ }
+
+ s.frame_width_bits_minus_1 = r.0.read_bits::(4)? as u8;
+ s.frame_height_bits_minus_1 = r.0.read_bits::(4)? as u8;
+ // frame_width_bits_minus_1 has been read from 4 bits, meaning we can read 16 bits at most.
+ s.max_frame_width_minus_1 =
+ r.0.read_bits::(s.frame_width_bits_minus_1 as usize + 1)? as u16;
+ // frame_height_bits_minus_1 has been read from 4 bits, meaning we can read 16 bits at most.
+ s.max_frame_height_minus_1 =
+ r.0.read_bits::(s.frame_height_bits_minus_1 as usize + 1)? as u16;
+ if s.reduced_still_picture_header {
+ s.frame_id_numbers_present_flag = false;
+ } else {
+ s.frame_id_numbers_present_flag = r.0.read_bit()?;
+ }
+ if s.frame_id_numbers_present_flag {
+ s.delta_frame_id_length_minus_2 = r.0.read_bits::(4)?;
+ s.additional_frame_id_length_minus_1 = r.0.read_bits::(3)?;
+ let frame_id_length =
+ s.additional_frame_id_length_minus_1 + s.delta_frame_id_length_minus_2 + 3;
+ if frame_id_length > 16 {
+ return Err(format!("Invalid frame_id_length {}", frame_id_length));
+ }
+ }
+
+ s.use_128x128_superblock = r.0.read_bit()?;
+ s.enable_filter_intra = r.0.read_bit()?;
+ s.enable_intra_edge_filter = r.0.read_bit()?;
+ if s.reduced_still_picture_header {
+ s.enable_interintra_compound = false;
+ s.enable_masked_compound = false;
+ s.enable_warped_motion = false;
+ s.enable_dual_filter = false;
+ s.enable_order_hint = false;
+ s.enable_jnt_comp = false;
+ s.enable_ref_frame_mvs = false;
+ s.seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS as _;
+ s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
+ s.order_hint_bits = 0;
+ s.order_hint_bits_minus_1 = -1;
+ } else {
+ s.enable_interintra_compound = r.0.read_bit()?;
+ s.enable_masked_compound = r.0.read_bit()?;
+ s.enable_warped_motion = r.0.read_bit()?;
+ s.enable_dual_filter = r.0.read_bit()?;
+ s.enable_order_hint = r.0.read_bit()?;
+ if s.enable_order_hint {
+ s.enable_jnt_comp = r.0.read_bit()?;
+ s.enable_ref_frame_mvs = r.0.read_bit()?;
+ } else {
+ s.enable_jnt_comp = false;
+ s.enable_ref_frame_mvs = false;
+ }
+ s.seq_choose_screen_content_tools = r.0.read_bit()?;
+ if s.seq_choose_screen_content_tools {
+ s.seq_force_screen_content_tools = SELECT_SCREEN_CONTENT_TOOLS as _;
+ } else {
+ s.seq_force_screen_content_tools = r.0.read_bit()? as _;
+ }
+ if s.seq_force_screen_content_tools > 0 {
+ s.seq_choose_integer_mv = r.0.read_bit()?;
+ if s.seq_choose_integer_mv {
+ s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
+ } else {
+ s.seq_force_integer_mv = r.0.read_bit()? as _;
+ }
+ } else {
+ s.seq_force_integer_mv = SELECT_INTEGER_MV as _;
+ }
+
+ if s.enable_order_hint {
+ s.order_hint_bits_minus_1 = r.0.read_bits::(3)?.try_into().unwrap();
+ s.order_hint_bits = s.order_hint_bits_minus_1 + 1;
+ } else {
+ s.order_hint_bits_minus_1 = -1;
+ s.order_hint_bits = 0;
+ }
+ }
+
+ s.enable_superres = r.0.read_bit()?;
+ s.enable_cdef = r.0.read_bit()?;
+ s.enable_restoration = r.0.read_bit()?;
+
+ Self::parse_color_config(&mut s, &mut r)?;
+
+ s.film_grain_params_present = r.0.read_bit()?;
+
+ Self::skip_and_check_trailing_bits(&mut r, obu)?;
+ let rc = Rc::new(s);
+ self.sequence_header = Some(rc.clone());
+
+ /* Client is supposed to set the operating point through external means,
+ * here we just set 0 as default. */
+ self.choose_operating_point(0)?;
+
+ Ok(rc)
+ }
+
+ /// Implements 7.21. Note that 7.20 will use the information from the
+ /// header, so we must save them now, as they will not be parsed from the
+ /// bitstream. We also save some internal parser state which will be useful
+ /// later.
+ fn load_reference_frame(&self, fh: &mut FrameHeaderObu) -> Result<(), String> {
+ let rf = &self.ref_info[fh.frame_to_show_map_idx as usize];
+
+ // Section 6.8.1: It is a requirement of bitstream conformance that a
+ // sequence header OBU has been received before a frame header OBU.
+ let seq = self.sequence()?;
+
+ /* at least save the sizes and for both kf and non-kf */
+ fh.frame_type = rf.ref_frame_type;
+ fh.upscaled_width = rf.ref_upscaled_width;
+ fh.frame_width = rf.ref_frame_width;
+ fh.frame_height = rf.ref_frame_height;
+ fh.render_width = rf.ref_render_width;
+ fh.render_height = rf.ref_render_height;
+
+ /* Save into the frame header */
+ if fh.frame_type == FrameType::KeyFrame {
+ fh.current_frame_id = rf.ref_frame_id;
+ /* We don't keep track of sequence information at the frame level */
+ fh.mi_cols = rf.ref_mi_cols;
+ fh.mi_rows = rf.ref_mi_rows;
+ /* The accelerator is keeping track of CDF values, so that is skipped too */
+ fh.global_motion_params = rf.global_motion_params.clone();
+
+ if seq.film_grain_params_present {
+ fh.film_grain_params = rf.film_grain_params.clone();
+ }
+ fh.loop_filter_params = rf.loop_filter_params.clone();
+ fh.segmentation_params = rf.segmentation_params.clone();
+ }
+
+ Ok(())
+ }
+
+ fn setup_past_independence(fh: &mut FrameHeaderObu) {
+ fh.segmentation_params.feature_enabled = Default::default();
+ fh.segmentation_params.feature_data = Default::default();
+
+ for i in ReferenceFrameType::Last as usize..ReferenceFrameType::AltRef as usize {
+ fh.global_motion_params.gm_type[i] = WarpModelType::Identity;
+ }
+
+ fh.loop_filter_params.loop_filter_delta_enabled = true;
+ fh.loop_filter_params.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
+ fh.loop_filter_params.loop_filter_mode_deltas = Default::default();
+ }
+
+ fn parse_tile_info(&mut self, r: &mut Reader, ti: &mut TileInfo) -> Result<(), String> {
+ let seq = self.sequence()?;
+
+ let sb_cols = if seq.use_128x128_superblock {
+ (self.mi_cols + 31) >> 5
+ } else {
+ (self.mi_cols + 15) >> 4
+ };
+
+ let sb_rows = if seq.use_128x128_superblock {
+ (self.mi_rows + 31) >> 5
+ } else {
+ (self.mi_rows + 15) >> 4
+ };
+
+ let sb_shift = if seq.use_128x128_superblock { 5 } else { 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 = helpers::tile_log2(max_tile_width_sb, sb_cols);
+
+ let max_log2_tile_cols =
+ helpers::tile_log2(1, std::cmp::min(sb_cols, MAX_TILE_COLS as u32));
+
+ let max_log2_tile_rows =
+ helpers::tile_log2(1, std::cmp::min(sb_rows, MAX_TILE_ROWS as u32));
+
+ let min_log2_tiles = std::cmp::max(
+ min_log2_tile_cols,
+ helpers::tile_log2(max_tile_area_sb, sb_rows * sb_cols),
+ );
+
+ ti.uniform_tile_spacing_flag = r.0.read_bit()?;
+
+ if ti.uniform_tile_spacing_flag {
+ self.tile_cols_log2 = min_log2_tile_cols;
+ while self.tile_cols_log2 < max_log2_tile_cols {
+ let increment_tile_cols_log_2 = r.0.read_bit()?;
+ if increment_tile_cols_log_2 {
+ self.tile_cols_log2 += 1;
+ } else {
+ break;
+ }
+ }
+
+ let tile_width_sb = (sb_cols + (1 << self.tile_cols_log2) - 1) >> self.tile_cols_log2;
+
+ let mut i = 0;
+ let mut start_sb = 0;
+
+ while start_sb < sb_cols {
+ self.mi_col_starts[i] = start_sb << sb_shift;
+ i += 1;
+ start_sb += tile_width_sb;
+ }
+
+ self.mi_col_starts[i] = self.mi_cols;
+ self.tile_cols = i as _;
+
+ if self.tile_cols > MAX_TILE_COLS as u32 {
+ return Err(format!("Invalid tile_cols {}", self.tile_cols));
+ }
+
+ /* compute this anyways */
+ while i >= 1 {
+ ti.width_in_sbs_minus_1[i - 1] =
+ ((self.mi_col_starts[i] - self.mi_col_starts[i - 1] + ((1 << sb_shift) - 1))
+ >> sb_shift)
+ - 1;
+ i -= 1;
+ }
+
+ let min_log2_tile_rows =
+ std::cmp::max(min_log2_tiles.saturating_sub(self.tile_cols_log2), 0);
+ self.tile_rows_log2 = min_log2_tile_rows;
+
+ while self.tile_rows_log2 < max_log2_tile_rows {
+ let increment_tile_rows_log_2 = r.0.read_bit()?;
+
+ if increment_tile_rows_log_2 {
+ self.tile_rows_log2 += 1;
+ } else {
+ break;
+ }
+ }
+
+ let tile_height_sb = (sb_rows + (1 << self.tile_rows_log2) - 1) >> self.tile_rows_log2;
+
+ let mut i = 0;
+ let mut start_sb = 0;
+
+ while start_sb < sb_rows {
+ self.mi_row_starts[i] = start_sb << sb_shift;
+ i += 1;
+ start_sb += tile_height_sb;
+ }
+
+ self.mi_row_starts[i] = self.mi_rows;
+ self.tile_rows = i as _;
+
+ if self.tile_rows > MAX_TILE_ROWS as u32 {
+ return Err(format!("Invalid tile_rows {}", self.tile_cols));
+ }
+
+ /* compute this anyways */
+ while i >= 1 {
+ ti.height_in_sbs_minus_1[i - 1] =
+ ((self.mi_row_starts[i] - self.mi_row_starts[i - 1] + ((1 << sb_shift) - 1))
+ >> sb_shift)
+ - 1;
+ i -= 1;
+ }
+ } else {
+ let mut widest_tile_sb = 0;
+ let mut start_sb = 0;
+ let mut i = 0;
+
+ while start_sb < sb_cols {
+ self.mi_col_starts[i] = start_sb << sb_shift;
+
+ let max_width = std::cmp::min(sb_cols - start_sb, max_tile_width_sb);
+ ti.width_in_sbs_minus_1[i] = r.read_ns(max_width.try_into().unwrap())?;
+
+ let size_sb = ti.width_in_sbs_minus_1[i] + 1;
+ widest_tile_sb = std::cmp::max(size_sb, widest_tile_sb);
+
+ start_sb += size_sb;
+ i += 1;
+ }
+
+ self.mi_col_starts[i] = self.mi_cols;
+ self.tile_cols = i as _;
+ self.tile_cols_log2 = helpers::tile_log2(1, self.tile_cols);
+
+ 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 = std::cmp::max(max_tile_area_sb / widest_tile_sb, 1);
+ let mut start_sb = 0;
+ let mut i = 0;
+ while start_sb < sb_rows {
+ self.mi_row_starts[i] = start_sb << sb_shift;
+ let max_height = std::cmp::min(sb_rows - start_sb, max_tile_height_sb);
+ ti.height_in_sbs_minus_1[i] = r.read_ns(max_height.try_into().unwrap())?;
+
+ let size_sb = ti.height_in_sbs_minus_1[i] + 1;
+ start_sb += size_sb;
+ i += 1;
+ }
+
+ self.mi_row_starts[i] = self.mi_rows;
+ self.tile_rows = i as _;
+ self.tile_rows_log2 = helpers::tile_log2(1, self.tile_rows);
+ }
+
+ if self.tile_cols_log2 > 0 || self.tile_rows_log2 > 0 {
+ let num_bits: usize = (self.tile_rows_log2 + self.tile_cols_log2)
+ .try_into()
+ .unwrap();
+ ti.context_update_tile_id = r.0.read_bits::(num_bits)?;
+
+ if ti.context_update_tile_id >= self.tile_rows * self.tile_cols {
+ return Err(format!(
+ "Invalid context_update_tile_id {}",
+ ti.context_update_tile_id
+ ));
+ }
+ self.tile_size_bytes = r.0.read_bits::(2)? + 1;
+ } else {
+ ti.context_update_tile_id = 0;
+ }
+
+ ti.mi_col_starts = self.mi_col_starts;
+ ti.mi_row_starts = self.mi_row_starts;
+ ti.tile_cols_log2 = self.tile_cols_log2;
+ ti.tile_cols = self.tile_cols;
+ ti.tile_rows_log2 = self.tile_rows_log2;
+ ti.tile_rows = self.tile_rows;
+ ti.tile_size_bytes = self.tile_size_bytes;
+
+ Ok(())
+ }
+
+ fn parse_quantization_params(
+ r: &mut Reader,
+ q: &mut QuantizationParams,
+ num_planes: u32,
+ separate_uv_delta_q: bool,
+ ) -> Result<(), String> {
+ q.base_q_idx = r.0.read_bits::(8)?;
+ q.delta_q_y_dc = r.read_delta_q()?;
+ if num_planes > 1 {
+ if separate_uv_delta_q {
+ q.diff_uv_delta = r.0.read_bit()?;
+ } else {
+ q.diff_uv_delta = false;
+ }
+
+ q.delta_q_u_dc = r.read_delta_q()?;
+ q.delta_q_u_ac = r.read_delta_q()?;
+ if q.diff_uv_delta {
+ q.delta_q_v_dc = r.read_delta_q()?;
+ q.delta_q_v_ac = r.read_delta_q()?;
+ } else {
+ q.delta_q_v_dc = q.delta_q_u_dc;
+ q.delta_q_v_ac = q.delta_q_u_ac;
+ }
+ } else {
+ q.delta_q_u_dc = 0;
+ q.delta_q_u_ac = 0;
+ q.delta_q_v_dc = 0;
+ q.delta_q_v_ac = 0;
+ }
+
+ q.using_qmatrix = r.0.read_bit()?;
+ if q.using_qmatrix {
+ q.qm_y = r.0.read_bits::(4)?;
+ q.qm_u = r.0.read_bits::(4)?;
+ if !separate_uv_delta_q {
+ q.qm_v = q.qm_u;
+ } else {
+ q.qm_v = r.0.read_bits::(4)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn parse_delta_q_params(r: &mut Reader, q: &mut QuantizationParams) -> Result<(), String> {
+ q.delta_q_res = 0;
+ q.delta_q_present = false;
+ if q.base_q_idx > 0 {
+ q.delta_q_present = r.0.read_bit()?;
+ }
+ if q.delta_q_present {
+ q.delta_q_res = r.0.read_bits::(2)?;
+ }
+
+ Ok(())
+ }
+
+ fn parse_delta_lf_params(
+ r: &mut Reader,
+ lf: &mut LoopFilterParams,
+ delta_q_present: bool,
+ allow_intrabc: bool,
+ ) -> Result<(), String> {
+ lf.delta_lf_present = false;
+ lf.delta_lf_res = 0;
+ lf.delta_lf_multi = false;
+ if delta_q_present {
+ if !allow_intrabc {
+ lf.delta_lf_present = r.0.read_bit()?;
+ }
+ if lf.delta_lf_present {
+ lf.delta_lf_res = r.0.read_bits::(2)? as u8;
+ lf.delta_lf_multi = r.0.read_bit()?;
+ }
+ }
+ Ok(())
+ }
+
+ fn parse_segmentation_params(
+ &self,
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ ) -> Result<(), String> {
+ let s = &mut fh.segmentation_params;
+ s.segmentation_enabled = r.0.read_bit()?;
+ if s.segmentation_enabled {
+ if fh.primary_ref_frame == PRIMARY_REF_NONE {
+ s.segmentation_update_map = true;
+ s.segmentation_temporal_update = false;
+ s.segmentation_update_data = true;
+ } else {
+ s.segmentation_update_map = r.0.read_bit()?;
+ if s.segmentation_update_map {
+ s.segmentation_temporal_update = r.0.read_bit()?;
+ }
+ s.segmentation_update_data = r.0.read_bit()?;
+ }
+ if s.segmentation_update_data {
+ for i in 0..MAX_SEGMENTS {
+ for j in 0..SEG_LVL_MAX {
+ let feature_enabled = r.0.read_bit()?;
+ s.feature_enabled[i][j] = feature_enabled;
+ if feature_enabled {
+ let bits_to_read = FEATURE_BITS[j];
+ let limit = FEATURE_MAX[j];
+ let signed = FEATURE_SIGNED[j];
+
+ if signed {
+ let feature_value = r.read_su(1 + bits_to_read as usize)?;
+ let clipped_value = helpers::clip3(-limit, limit, feature_value);
+ s.feature_data[i][j] = clipped_value as _;
+ } else {
+ let feature_value = r.0.read_bits::(bits_to_read as usize)?;
+ let clipped_value = helpers::clip3(
+ 0,
+ limit,
+ feature_value
+ .try_into()
+ .map_err(|_| "Invalid feature_value")?,
+ );
+ s.feature_data[i][j] = clipped_value as _;
+ }
+ }
+ }
+ }
+ } else {
+ /* copy from prev_frame */
+ let prev_frame =
+ &self.ref_info[fh.ref_frame_idx[fh.primary_ref_frame as usize] as usize];
+
+ if !prev_frame.ref_valid {
+ return Err("Reference is invalid".into());
+ }
+
+ s.feature_enabled = prev_frame.segmentation_params.feature_enabled;
+ s.feature_data = prev_frame.segmentation_params.feature_data;
+ }
+ } else {
+ for i in 0..MAX_SEGMENTS {
+ for j in 0..SEG_LVL_MAX {
+ s.feature_enabled[i][j] = false;
+ s.feature_data[i][j] = 0;
+ }
+ }
+ }
+
+ s.seg_id_pre_skip = false;
+ s.last_active_seg_id = 0;
+ 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(())
+ }
+
+ fn parse_loop_filter_parameters(
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ num_planes: u32,
+ ) -> Result<(), String> {
+ let lf = &mut fh.loop_filter_params;
+ if fh.coded_lossless || fh.allow_intrabc {
+ lf.loop_filter_level[0] = 0;
+ lf.loop_filter_level[1] = 0;
+ lf.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
+
+ lf.loop_filter_mode_deltas = Default::default();
+
+ return Ok(());
+ }
+
+ lf.loop_filter_level[0] = r.0.read_bits::(6)? as u8;
+ lf.loop_filter_level[1] = r.0.read_bits::(6)? as u8;
+ if num_planes > 1 && (lf.loop_filter_level[0] > 0 || lf.loop_filter_level[1] > 0) {
+ lf.loop_filter_level[2] = r.0.read_bits::(6)? as u8;
+ lf.loop_filter_level[3] = r.0.read_bits::(6)? as u8;
+ }
+
+ lf.loop_filter_sharpness = r.0.read_bits::(3)? as u8;
+ lf.loop_filter_delta_enabled = r.0.read_bit()?;
+ if lf.loop_filter_delta_enabled {
+ lf.loop_filter_delta_update = r.0.read_bit()?;
+ if lf.loop_filter_delta_update {
+ for i in 0..TOTAL_REFS_PER_FRAME {
+ let update_ref_delta = r.0.read_bit()?;
+ if update_ref_delta {
+ lf.loop_filter_ref_deltas[i] = r.read_su(7)? as i8;
+ }
+ }
+
+ for i in 0..2 {
+ let update_mode_delta = r.0.read_bit()?;
+ if update_mode_delta {
+ lf.loop_filter_mode_deltas[i] = r.read_su(7)? as i8;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn parse_cdef_params(
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ enable_cdef: bool,
+ num_planes: u32,
+ ) -> Result<(), String> {
+ let cdef = &mut fh.cdef_params;
+
+ if fh.coded_lossless || fh.allow_intrabc || !enable_cdef {
+ cdef.cdef_bits = 0;
+ cdef.cdef_y_pri_strength[0] = 0;
+ cdef.cdef_y_sec_strength[0] = 0;
+ cdef.cdef_uv_pri_strength[0] = 0;
+ cdef.cdef_uv_sec_strength[0] = 0;
+ cdef.cdef_damping = 3;
+ return Ok(());
+ }
+
+ cdef.cdef_damping = r.0.read_bits::(2)? + 3;
+ cdef.cdef_bits = r.0.read_bits::(2)?;
+ for i in 0..(1 << cdef.cdef_bits) as usize {
+ cdef.cdef_y_pri_strength[i] = r.0.read_bits::(4)?;
+ cdef.cdef_y_sec_strength[i] = r.0.read_bits::(2)?;
+ if cdef.cdef_y_sec_strength[i] == 3 {
+ cdef.cdef_y_sec_strength[i] += 1;
+ }
+ if num_planes > 1 {
+ cdef.cdef_uv_pri_strength[i] = r.0.read_bits::(4)?;
+ cdef.cdef_uv_sec_strength[i] = r.0.read_bits::(2)?;
+ if cdef.cdef_uv_sec_strength[i] == 3 {
+ cdef.cdef_uv_sec_strength[i] += 1;
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn parse_loop_restoration_params(
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ enable_restoration: bool,
+ num_planes: u32,
+ use_128x128_superblock: bool,
+ subsampling_x: bool,
+ subsampling_y: bool,
+ ) -> Result<(), String> {
+ let lr = &mut fh.loop_restoration_params;
+
+ if fh.all_lossless || fh.allow_intrabc || !enable_restoration {
+ lr.frame_restoration_type[0] = FrameRestorationType::None;
+ lr.frame_restoration_type[1] = FrameRestorationType::None;
+ lr.frame_restoration_type[2] = FrameRestorationType::None;
+ lr.uses_lr = false;
+ return Ok(());
+ }
+
+ lr.uses_lr = false;
+ lr.uses_chroma_lr = false;
+
+ const REMAP_LR_TYPE: [FrameRestorationType; 4] = [
+ FrameRestorationType::None,
+ FrameRestorationType::Switchable,
+ FrameRestorationType::Wiener,
+ FrameRestorationType::Sgrproj,
+ ];
+
+ for i in 0..num_planes as usize {
+ let lr_type = r.0.read_bits::(2)?;
+ lr.frame_restoration_type[i] = REMAP_LR_TYPE[lr_type as usize];
+ if lr.frame_restoration_type[i] != FrameRestorationType::None {
+ lr.uses_lr = true;
+ if i > 0 {
+ lr.uses_chroma_lr = true;
+ }
+ }
+ }
+
+ if lr.uses_lr {
+ if use_128x128_superblock {
+ lr.lr_unit_shift = r.0.read_bits::(1)? as u8 + 1;
+ } else {
+ lr.lr_unit_shift = r.0.read_bits::(1)? as u8;
+ if lr.lr_unit_shift > 0 {
+ lr.lr_unit_shift += r.0.read_bits::(1)? as u8;
+ }
+ }
+
+ lr.loop_restoration_size[0] = RESTORATION_TILESIZE_MAX >> (2 - lr.lr_unit_shift);
+ if subsampling_x && subsampling_y && lr.uses_chroma_lr {
+ lr.lr_uv_shift = r.0.read_bits::(1)? as u8;
+ } else {
+ lr.lr_uv_shift = 0;
+ }
+
+ lr.loop_restoration_size[1] = lr.loop_restoration_size[0] >> lr.lr_uv_shift;
+ lr.loop_restoration_size[2] = lr.loop_restoration_size[0] >> lr.lr_uv_shift;
+ }
+
+ Ok(())
+ }
+
+ fn read_tx_mode(r: &mut Reader, fh: &mut FrameHeaderObu) -> Result<(), String> {
+ if fh.coded_lossless {
+ fh.tx_mode = TxMode::Only4x4;
+ } else {
+ let tx_mode_select = r.0.read_bit()?;
+
+ if tx_mode_select {
+ fh.tx_mode = TxMode::Select;
+ } else {
+ fh.tx_mode = TxMode::Largest;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn parse_skip_mode_params(
+ &self,
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ enable_order_hint: bool,
+ order_hint_bits: i32,
+ ) -> Result<(), String> {
+ let skip_mode_allowed;
+
+ if fh.frame_is_intra || !fh.reference_select || !enable_order_hint {
+ skip_mode_allowed = false;
+ } else {
+ let mut forward_idx = -1;
+ let mut backward_idx = -1;
+ let mut forward_hint = 0;
+ let mut backward_hint = 0;
+ for i in 0..REFS_PER_FRAME {
+ let ref_hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
+ if helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ fh.order_hint.try_into().unwrap(),
+ ) < 0
+ && (forward_idx < 0
+ || helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ forward_hint,
+ ) > 0)
+ {
+ forward_idx = i32::try_from(i).unwrap();
+ forward_hint = ref_hint.try_into().unwrap();
+ } else if helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ fh.order_hint.try_into().unwrap(),
+ ) > 0
+ && (backward_idx < 0 || {
+ helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ backward_hint,
+ ) < 0
+ })
+ {
+ backward_idx = i32::try_from(i).unwrap();
+ backward_hint = ref_hint.try_into().unwrap();
+ }
+ }
+
+ if forward_idx < 0 {
+ skip_mode_allowed = false;
+ } else if backward_idx >= 0 {
+ skip_mode_allowed = true;
+ fh.skip_mode_frame[0] = ReferenceFrameType::Last as u32
+ + u32::try_from(std::cmp::min(forward_idx, backward_idx)).unwrap();
+ fh.skip_mode_frame[1] = ReferenceFrameType::Last as u32
+ + u32::try_from(std::cmp::max(forward_idx, backward_idx)).unwrap();
+ } else {
+ let mut second_forward_idx = -1;
+ let mut second_forward_hint = 0;
+ for i in 0..REFS_PER_FRAME {
+ let ref_hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
+ if helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ forward_hint,
+ ) < 0
+ && (second_forward_idx < 0
+ || helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ ref_hint.try_into().unwrap(),
+ second_forward_hint,
+ ) > 0)
+ {
+ second_forward_idx = i32::try_from(i).unwrap();
+ second_forward_hint = ref_hint.try_into().unwrap();
+ }
+ }
+
+ if second_forward_idx < 0 {
+ skip_mode_allowed = false;
+ } else {
+ skip_mode_allowed = true;
+ fh.skip_mode_frame[0] = ReferenceFrameType::Last as u32
+ + u32::try_from(std::cmp::min(forward_idx, second_forward_idx)).unwrap();
+ fh.skip_mode_frame[1] = ReferenceFrameType::Last as u32
+ + u32::try_from(std::cmp::max(forward_idx, second_forward_idx)).unwrap();
+ }
+ }
+ }
+
+ if skip_mode_allowed {
+ fh.skip_mode_present = r.0.read_bit()?;
+ } else {
+ fh.skip_mode_present = false;
+ }
+
+ Ok(())
+ }
+
+ fn parse_frame_reference_mode(r: &mut Reader, fh: &mut FrameHeaderObu) -> Result<(), String> {
+ if fh.frame_is_intra {
+ fh.reference_select = false;
+ } else {
+ fh.reference_select = r.0.read_bit()?;
+ }
+ Ok(())
+ }
+
+ fn seg_feature_active_idx(seg: &SegmentationParams, idx: u32, feature: u32) -> bool {
+ seg.segmentation_enabled && seg.feature_enabled[idx as usize][feature as usize]
+ }
+
+ fn get_qindex(fh: &FrameHeaderObu, ignore_deltaq: bool, segment_id: u32) -> i32 {
+ let base_q_idx = i32::try_from(fh.quantization_params.base_q_idx).unwrap();
+ if Self::seg_feature_active_idx(&fh.segmentation_params, segment_id, SEG_LVL_ALT_Q as u32) {
+ let data = fh.segmentation_params.feature_data[segment_id as usize][SEG_LVL_ALT_Q];
+ let mut qindex = base_q_idx + i32::from(data);
+ if !ignore_deltaq && fh.quantization_params.delta_q_present {
+ qindex += i32::try_from(fh.quantization_params.delta_q_res).unwrap();
+ }
+ helpers::clip3(0, 255, qindex)
+ } else {
+ base_q_idx
+ }
+ }
+
+ fn setup_shear(warp_params: &[i32; 6]) -> Result {
+ let mut default = true;
+ for (i, param) in warp_params.iter().enumerate() {
+ let default_value = if i % 3 == 2 {
+ 1 << WARPEDMODEL_PREC_BITS
+ } else {
+ 0
+ };
+ if *param != default_value {
+ default = false;
+ break;
+ }
+ }
+
+ /* assume the default params to be valid */
+ if default {
+ return Ok(true);
+ }
+
+ let alpha0 = helpers::clip3(-32768, 32767, warp_params[2] - (1 << WARPEDMODEL_PREC_BITS));
+ let beta0 = helpers::clip3(-32768, 32767, warp_params[3]);
+
+ let (div_shift, div_factor) = helpers::resolve_divisor(warp_params[2])?;
+
+ let v = i64::from(warp_params[4] << WARPEDMODEL_PREC_BITS);
+ let v = (v * i64::from(div_factor)) as i32;
+ let gamma0 = helpers::clip3(-32678, 32767, helpers::round2signed(v, div_shift)?);
+
+ let w = warp_params[3] * warp_params[4];
+
+ let delta0 = helpers::clip3(
+ -32768,
+ 32767,
+ warp_params[5]
+ - helpers::round2signed(w * div_factor, div_shift)?
+ - (1 << WARPEDMODEL_PREC_BITS),
+ );
+
+ let alpha =
+ helpers::round2signed(alpha0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
+ let beta = helpers::round2signed(beta0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
+ let gamma =
+ helpers::round2signed(gamma0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
+ let delta =
+ helpers::round2signed(delta0, WARP_PARAM_REDUCE_BITS)? << WARP_PARAM_REDUCE_BITS;
+
+ #[allow(clippy::needless_bool)]
+ let warp_valid = if 4 * alpha.abs() + 7 * beta.abs() >= (1 << WARPEDMODEL_PREC_BITS)
+ || 4 * gamma.abs() + 4 * delta.abs() >= (1 << WARPEDMODEL_PREC_BITS)
+ {
+ false
+ } else {
+ true
+ };
+
+ Ok(warp_valid)
+ }
+
+ fn read_global_param(
+ reader: &mut Reader,
+ type_: WarpModelType,
+ ref_frame: usize,
+ idx: usize,
+ allow_high_precision_mv: bool,
+ prev_gm_params: &[[i32; 6]; NUM_REF_FRAMES],
+ gm_params: &mut [[i32; 6]; NUM_REF_FRAMES],
+ ) -> Result<(), String> {
+ let mut abs_bits = GM_ABS_ALPHA_BITS;
+ let mut prec_bits = GM_ALPHA_PREC_BITS;
+ if idx < 2 {
+ if type_ == WarpModelType::Translation {
+ abs_bits = GM_ABS_TRANS_ONLY_BITS - !allow_high_precision_mv as u32;
+ prec_bits = GM_TRANS_ONLY_PREC_BITS - !allow_high_precision_mv as u32;
+ } else {
+ abs_bits = GM_ABS_TRANS_BITS;
+ prec_bits = GM_TRANS_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 r = (prev_gm_params[ref_frame][idx] >> prec_diff) - sub;
+ gm_params[ref_frame][idx] =
+ (reader.decode_signed_subexp_with_ref(-mx, mx + 1, r)? << prec_diff) + round;
+
+ Ok(())
+ }
+
+ fn parse_global_motion_params(
+ &mut self,
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ ) -> Result<(), String> {
+ let gm = &mut fh.global_motion_params;
+ let mut type_;
+ let mut prev_gm_params: [[i32; 6]; NUM_REF_FRAMES] = Default::default();
+
+ for ref_frame in ReferenceFrameType::Last as usize..=ReferenceFrameType::AltRef as usize {
+ gm.gm_type[ref_frame] = WarpModelType::Identity;
+ for i in 0..6 {
+ gm.gm_params[ref_frame][i] = if i % 3 == 2 {
+ 1 << WARPEDMODEL_PREC_BITS
+ } else {
+ 0
+ }
+ }
+ gm.warp_valid[ref_frame] = true;
+ }
+
+ if fh.frame_is_intra {
+ return Ok(());
+ }
+
+ // Following libgav1: implement part of setup_past_independence() and
+ // load_previous(), i.e.: the parts that refer to the global motion
+ // parameters.
+ if fh.primary_ref_frame == PRIMARY_REF_NONE {
+ // setup_past_independence()
+ #[allow(clippy::needless_range_loop)]
+ for ref_frame in ReferenceFrameType::Last as usize..ReferenceFrameType::AltRef as usize
+ {
+ for i in 0..5 {
+ prev_gm_params[ref_frame][i] = if i % 3 == 2 {
+ 1 << WARPEDMODEL_PREC_BITS
+ } else {
+ 0
+ }
+ }
+ }
+ } else {
+ // load_previous():
+ // 1. The variable prevFrame is set equal to ref_frame_idx[ primary_ref_frame ].
+ // 2. PrevGmParams is set equal to SavedGmParams[ prevFrame ].
+ let prev_frame = fh.ref_frame_idx[fh.primary_ref_frame as usize];
+ prev_gm_params = self.ref_info[prev_frame as usize]
+ .global_motion_params
+ .gm_params;
+ }
+
+ for ref_frame in ReferenceFrameType::Last as usize..=ReferenceFrameType::AltRef as usize {
+ gm.is_global[ref_frame] = r.0.read_bit()?;
+ if gm.is_global[ref_frame] {
+ gm.is_rot_zoom[ref_frame] = r.0.read_bit()?;
+ if gm.is_rot_zoom[ref_frame] {
+ type_ = WarpModelType::RotZoom;
+ } else {
+ gm.is_translation[ref_frame] = r.0.read_bit()?;
+ if gm.is_translation[ref_frame] {
+ type_ = WarpModelType::Translation;
+ } else {
+ type_ = WarpModelType::Affine;
+ }
+ }
+ } else {
+ type_ = WarpModelType::Identity;
+ }
+
+ gm.gm_type[ref_frame] = type_;
+ if gm.gm_type[ref_frame] as u32 >= WarpModelType::RotZoom as u32 {
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 2,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 3,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+
+ if type_ == WarpModelType::Affine {
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 4,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 5,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+ } else {
+ gm.gm_params[ref_frame][4] = -gm.gm_params[ref_frame][3];
+ gm.gm_params[ref_frame][5] = gm.gm_params[ref_frame][2];
+ }
+ }
+
+ if gm.gm_type[ref_frame] as u32 >= WarpModelType::Translation as u32 {
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 0,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+
+ Self::read_global_param(
+ r,
+ type_,
+ ref_frame,
+ 1,
+ fh.allow_high_precision_mv,
+ &prev_gm_params,
+ &mut gm.gm_params,
+ )?;
+ }
+
+ gm.warp_valid[ref_frame] = Self::setup_shear(&gm.gm_params[ref_frame])?;
+ }
+
+ Ok(())
+ }
+
+ fn parse_film_grain_parameters(
+ &self,
+ r: &mut Reader,
+ fh: &mut FrameHeaderObu,
+ film_grain_params_present: bool,
+ mono_chrome: bool,
+ subsampling_x: bool,
+ subsampling_y: bool,
+ ) -> Result<(), String> {
+ let fg = &mut fh.film_grain_params;
+
+ if !film_grain_params_present || (!fh.show_frame && !fh.showable_frame) {
+ *fg = Default::default();
+ return Ok(());
+ }
+
+ fg.apply_grain = r.0.read_bit()?;
+ if !fg.apply_grain {
+ *fg = Default::default();
+ return Ok(());
+ }
+
+ fg.grain_seed = r.0.read_bits::(16)? as u16;
+ if fh.frame_type == FrameType::InterFrame {
+ fg.update_grain = r.0.read_bit()?;
+ } else {
+ fg.update_grain = true;
+ }
+
+ if !fg.update_grain {
+ fg.film_grain_params_ref_idx = r.0.read_bits::(3)? as u8;
+ let temp_grain_seed = fg.grain_seed;
+
+ if !fh
+ .ref_frame_idx
+ .iter()
+ .any(|&ref_frame_idx| ref_frame_idx == fg.film_grain_params_ref_idx)
+ {
+ return Err("Invalid film_grain_params_ref_idx".into());
+ }
+
+ // load_grain_params()
+ *fg = self.ref_info[fg.film_grain_params_ref_idx as usize]
+ .film_grain_params
+ .clone();
+
+ fg.grain_seed = temp_grain_seed;
+
+ return Ok(());
+ }
+
+ fg.num_y_points = r.0.read_bits::(4)? as u8;
+ fg.point_y_value
+ .iter_mut()
+ .zip(fg.point_y_scaling.iter_mut())
+ .take(fg.num_y_points as usize)
+ .try_for_each(|(point_y_value, point_y_scaling)| {
+ *point_y_value = r.0.read_bits::(8)? as u8;
+ *point_y_scaling = r.0.read_bits::(8)? as u8;
+ Ok::<_, String>(())
+ })?;
+
+ if mono_chrome {
+ fg.chroma_scaling_from_luma = false;
+ } else {
+ fg.chroma_scaling_from_luma = r.0.read_bit()?;
+ }
+
+ if mono_chrome
+ || fg.chroma_scaling_from_luma
+ || (subsampling_x && subsampling_y && fg.num_y_points == 0)
+ {
+ fg.num_cb_points = 0;
+ fg.num_cr_points = 0;
+ } else {
+ fg.num_cb_points = r.0.read_bits::(4)? as u8;
+ if fg.num_cb_points > 10 {
+ return Err(format!("Invalid num_cb_points {}", fg.num_cb_points));
+ }
+
+ for i in 0..fg.num_cb_points as usize {
+ fg.point_cb_value[i] = r.0.read_bits::(8)? as u8;
+ if i > 0 && fg.point_cb_value[i - 1] >= fg.point_cb_value[i] {
+ return Err(format!(
+ "Invalid point_cb_value[{}] {}",
+ i, fg.point_cb_value[i]
+ ));
+ }
+ fg.point_cb_scaling[i] = r.0.read_bits::(8)? as u8;
+ }
+
+ fg.num_cr_points = r.0.read_bits::(4)? as u8;
+ for i in 0..fg.num_cr_points as usize {
+ fg.point_cr_value[i] = r.0.read_bits::(8)? as u8;
+ if i > 0 && fg.point_cr_value[i - 1] >= fg.point_cr_value[i] {
+ return Err(format!(
+ "Invalid point_cr_value[{}] {}",
+ i, fg.point_cr_value[i]
+ ));
+ }
+ fg.point_cr_scaling[i] = r.0.read_bits::(8)? as u8;
+ }
+ }
+
+ fg.grain_scaling_minus_8 = r.0.read_bits::(2)? as u8;
+ fg.ar_coeff_lag = r.0.read_bits::(2)?;
+
+ let num_pos_luma = 2 * fg.ar_coeff_lag * (fg.ar_coeff_lag + 1);
+ let num_pos_chroma = if fg.num_y_points > 0 {
+ for i in 0..num_pos_luma as usize {
+ fg.ar_coeffs_y_plus_128[i] = r.0.read_bits::(8)? as u8;
+ }
+ num_pos_luma + 1
+ } else {
+ num_pos_luma
+ };
+
+ if fg.chroma_scaling_from_luma || fg.num_cb_points > 0 {
+ for i in 0..num_pos_chroma as usize {
+ fg.ar_coeffs_cb_plus_128[i] = r.0.read_bits::(8)? as u8;
+ }
+ }
+
+ if fg.chroma_scaling_from_luma || fg.num_cr_points > 0 {
+ for i in 0..num_pos_chroma as usize {
+ fg.ar_coeffs_cr_plus_128[i] = r.0.read_bits::(8)? as u8;
+ }
+ }
+
+ fg.ar_coeff_shift_minus_6 = r.0.read_bits::(2)? as u8;
+ fg.grain_scale_shift = r.0.read_bits::(2)? as u8;
+
+ if fg.num_cb_points > 0 {
+ fg.cb_mult = r.0.read_bits::(8)? as u8;
+ fg.cb_luma_mult = r.0.read_bits::(8)? as u8;
+ fg.cb_offset = r.0.read_bits::(9)? as u16;
+ }
+
+ if fg.num_cr_points > 0 {
+ fg.cr_mult = r.0.read_bits::(8)? as u8;
+ fg.cr_luma_mult = r.0.read_bits::(8)? as u8;
+ fg.cr_offset = r.0.read_bits::(9)? as u16;
+ }
+
+ fg.overlap_flag = r.0.read_bit()?;
+ fg.clip_to_restricted_range = r.0.read_bit()?;
+
+ Ok(())
+ }
+
+ fn sequence(&self) -> Result<&SequenceHeaderObu, String> {
+ let Some(seq) = self.sequence_header.as_ref() else {
+ return Err("No sequence header parsed yet".into());
+ };
+
+ Ok(seq)
+ }
+
+ fn parse_uncompressed_frame_header(&mut self, obu: &Obu) -> Result {
+ let mut r = Reader::new(obu.as_ref());
+
+ let mut fh = FrameHeaderObu {
+ obu_header: obu.header.clone(),
+ ..Default::default()
+ };
+
+ // Section 6.8.1: It is a requirement of bitstream conformance that a
+ // sequence header OBU has been received before a frame header OBU.
+ let &SequenceHeaderObu {
+ operating_points_cnt_minus_1,
+ seq_force_integer_mv,
+ additional_frame_id_length_minus_1,
+ delta_frame_id_length_minus_2,
+ decoder_model_info_present_flag,
+ reduced_still_picture_header,
+ frame_id_numbers_present_flag,
+ use_128x128_superblock,
+ enable_order_hint,
+ seq_force_screen_content_tools,
+ order_hint_bits,
+ enable_cdef,
+ enable_restoration,
+ enable_warped_motion,
+ color_config:
+ ColorConfig {
+ subsampling_x,
+ subsampling_y,
+ separate_uv_delta_q,
+ mono_chrome,
+ ..
+ },
+ timing_info:
+ TimingInfo {
+ equal_picture_interval,
+ ..
+ },
+ decoder_model_info:
+ DecoderModelInfo {
+ frame_presentation_time_length_minus_1,
+ buffer_removal_time_length_minus_1,
+ ..
+ },
+ num_planes,
+ film_grain_params_present,
+ ..
+ } = self.sequence()?;
+
+ let mut id_len = 0;
+
+ if frame_id_numbers_present_flag {
+ id_len = additional_frame_id_length_minus_1 + delta_frame_id_length_minus_2 + 3;
+ }
+
+ const ALL_FRAMES: u32 = (1 << NUM_REF_FRAMES) - 1;
+
+ if reduced_still_picture_header {
+ fh.show_existing_frame = false;
+ fh.frame_type = FrameType::KeyFrame;
+ fh.frame_is_intra = true;
+ fh.show_frame = true;
+ fh.showable_frame = false;
+ } else {
+ fh.show_existing_frame = r.0.read_bit()?;
+ if matches!(obu.header.obu_type, ObuType::Frame) && fh.show_existing_frame {
+ return Err("If obu_type is equal to OBU_FRAME, it is a requirement of bitstream conformance that show_existing_frame is equal to 0.".into());
+ }
+ if fh.show_existing_frame {
+ fh.frame_to_show_map_idx = r.0.read_bits::(3)? as u8;
+
+ if decoder_model_info_present_flag && !equal_picture_interval {
+ fh.frame_presentation_time =
+ r.0.read_bits::(frame_presentation_time_length_minus_1 as usize + 1)?;
+ }
+
+ let ref_frame = &self.ref_info[fh.frame_to_show_map_idx as usize];
+
+ fh.refresh_frame_flags = 0;
+ if frame_id_numbers_present_flag {
+ if id_len == 0 {
+ return Err(format!("Invalid id_len {}", id_len));
+ }
+ fh.display_frame_id = r.0.read_bits::(id_len.try_into().unwrap())?;
+ if ref_frame.display_frame_id != fh.display_frame_id || !ref_frame.ref_valid {
+ return Err("Invalid display_frame_id".into());
+ }
+ }
+
+ if !ref_frame.showable_frame {
+ return Err("Invalid bitstream: can't show this past frame".into());
+ }
+
+ // In decode_frame_wrapup():
+ //
+ // Otherwise (show_existing_frame is equal to 1), if frame_type
+ // is equal to KEY_FRAME, the reference frame loading process as
+ // specified in section 7.21 is invoked (this process loads
+ // frame state from the reference frames into the current frame
+ // state variables)
+ //
+ // The following ordered steps now apply:
+ //
+ // 1. The reference frame update process as specified in section
+ // 7.20 is invoked (this process saves the current frame state
+ // into the reference frames).
+ //
+ // 2. If show_frame is equal to 1 or show_existing_frame is
+ // equal to 1, the output process as specified in section 7.18
+ // is invoked (this will output the current frame or a saved
+ // frame).
+ //
+ // We implement 1. here while 2. is left to the actual decoder
+ self.load_reference_frame(&mut fh)?;
+ if fh.frame_type == FrameType::KeyFrame {
+ fh.refresh_frame_flags = ALL_FRAMES;
+ }
+
+ if film_grain_params_present {
+ // load_grain_params()
+ fh.film_grain_params = self.ref_info[fh.frame_to_show_map_idx as usize]
+ .film_grain_params
+ .clone();
+ }
+
+ // See 5.10.
+ if matches!(obu.header.obu_type, ObuType::Frame) {
+ r.byte_alignment()?;
+ }
+
+ fh.header_bytes = usize::try_from(r.0.position() / 8).unwrap();
+ return Ok(fh);
+ }
+
+ fh.frame_type = FrameType::try_from(r.0.read_bits::(2)?)?;
+ fh.frame_is_intra = matches!(
+ fh.frame_type,
+ FrameType::IntraOnlyFrame | FrameType::KeyFrame
+ );
+
+ fh.show_frame = r.0.read_bit()?;
+
+ if fh.show_frame && decoder_model_info_present_flag && equal_picture_interval {
+ fh.frame_presentation_time =
+ r.0.read_bits::(frame_presentation_time_length_minus_1 as usize + 1)?;
+ }
+
+ if fh.show_frame {
+ fh.showable_frame = !matches!(fh.frame_type, FrameType::KeyFrame);
+ } else {
+ fh.showable_frame = r.0.read_bit()?;
+ }
+
+ if fh.frame_type == FrameType::SwitchFrame
+ || (fh.frame_type == FrameType::KeyFrame && fh.show_frame)
+ {
+ fh.error_resilient_mode = true;
+ } else {
+ fh.error_resilient_mode = r.0.read_bit()?;
+ }
+ }
+
+ if fh.frame_type == FrameType::KeyFrame && fh.show_frame {
+ for i in 0..NUM_REF_FRAMES {
+ self.ref_info[i].ref_valid = false;
+ self.ref_info[i].ref_order_hint = 0;
+ }
+ for i in 0..REFS_PER_FRAME {
+ fh.order_hints[ReferenceFrameType::Last as usize + i] = 0;
+ }
+ }
+
+ fh.disable_cdf_update = r.0.read_bit()?;
+ if seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS as u32 {
+ fh.allow_screen_content_tools = r.0.read_bit()? as u32;
+ } else {
+ fh.allow_screen_content_tools = seq_force_screen_content_tools;
+ }
+
+ if fh.allow_screen_content_tools > 0 {
+ if seq_force_integer_mv == SELECT_INTEGER_MV as u32 {
+ fh.force_integer_mv = r.0.read_bit()? as u32;
+ } else {
+ fh.force_integer_mv = seq_force_integer_mv;
+ }
+ } else {
+ fh.force_integer_mv = 0;
+ }
+
+ if fh.frame_is_intra {
+ fh.force_integer_mv = 1;
+ }
+
+ if frame_id_numbers_present_flag {
+ self.prev_frame_id = self.current_frame_id;
+ self.current_frame_id = r.0.read_bits::(id_len.try_into().unwrap())?;
+ fh.current_frame_id = self.current_frame_id;
+
+ /* conformance checking, as per aom */
+ let have_prev_frame_id =
+ !(self.is_first_frame || fh.frame_type == FrameType::KeyFrame && fh.show_frame);
+
+ if have_prev_frame_id {
+ let frame_id_length =
+ additional_frame_id_length_minus_1 + delta_frame_id_length_minus_2 + 3;
+
+ let diff_frame_id = if self.current_frame_id > self.prev_frame_id {
+ self.current_frame_id - self.prev_frame_id
+ } else {
+ if frame_id_length > 16 {
+ return Err(format!("Invalid frame_id_length {}", frame_id_length));
+ }
+ (1 << frame_id_length) + self.current_frame_id - self.prev_frame_id
+ };
+
+ if self.prev_frame_id == self.current_frame_id
+ || diff_frame_id >= (1 << (frame_id_length - 1))
+ {
+ return Err(format!(
+ "Invalid frame_id: prev_frame_id = {}, current_frame_id = {}",
+ self.prev_frame_id, self.current_frame_id
+ ));
+ }
+ }
+
+ /* mark_ref_frames (idLen) */
+ let diff_len = delta_frame_id_length_minus_2 + 2;
+ let shifted_diff_len = 1 << diff_len;
+ let shifted_id_len = 1 << id_len;
+
+ for i in 0..NUM_REF_FRAMES {
+ if self.current_frame_id > shifted_diff_len {
+ if self.ref_info[i].ref_frame_id > self.current_frame_id
+ || self.ref_info[i].ref_frame_id
+ < (self.current_frame_id - shifted_diff_len)
+ {
+ self.ref_info[i].ref_valid = false;
+ }
+ } else if self.ref_info[i].ref_frame_id > self.current_frame_id
+ && self.ref_info[i].ref_frame_id
+ < shifted_id_len + self.current_frame_id - shifted_diff_len
+ {
+ self.ref_info[i].ref_valid = false;
+ }
+ }
+ } else {
+ self.current_frame_id = 0;
+ self.prev_frame_id = self.current_frame_id;
+ fh.current_frame_id = self.current_frame_id;
+ }
+
+ if fh.frame_type == FrameType::SwitchFrame {
+ fh.frame_size_override_flag = true;
+ } else if reduced_still_picture_header {
+ fh.frame_size_override_flag = false;
+ } else {
+ fh.frame_size_override_flag = r.0.read_bit()?;
+ }
+
+ fh.order_hint = r.0.read_bits::(order_hint_bits.try_into().unwrap())?;
+
+ if fh.frame_is_intra || fh.error_resilient_mode {
+ fh.primary_ref_frame = PRIMARY_REF_NONE;
+ } else {
+ fh.primary_ref_frame = r.0.read_bits::(3)?;
+ }
+
+ let operating_points = &self.sequence()?.operating_points;
+ if decoder_model_info_present_flag {
+ fh.buffer_removal_time_present_flag = r.0.read_bit()?;
+ if fh.buffer_removal_time_present_flag {
+ #[allow(clippy::needless_range_loop)]
+ for op_num in 0..=operating_points_cnt_minus_1 as usize {
+ if operating_points[op_num].decoder_model_present_for_this_op {
+ let op_pt_idc = operating_points[op_num].idc;
+ let in_temporal_layer = (op_pt_idc >> fh.obu_header.temporal_id) & 1 != 0;
+ let in_spatial_layer =
+ (op_pt_idc >> (fh.obu_header.spatial_id + 8)) & 1 != 0;
+
+ if op_pt_idc == 0 || (in_temporal_layer && in_spatial_layer) {
+ let n = buffer_removal_time_length_minus_1 + 1;
+ fh.buffer_removal_time[op_num] = r.0.read_bits::(n as usize)?;
+ }
+ }
+ }
+ }
+ }
+
+ fh.allow_high_precision_mv = false;
+ fh.use_ref_frame_mvs = false;
+ fh.allow_intrabc = false;
+ if fh.frame_type == FrameType::SwitchFrame
+ || (fh.frame_type == FrameType::KeyFrame && fh.show_frame)
+ {
+ fh.refresh_frame_flags = ALL_FRAMES;
+ } else {
+ fh.refresh_frame_flags = r.0.read_bits::(8)?;
+ }
+
+ /* equivalent boolean expression */
+ if (!fh.frame_is_intra || fh.refresh_frame_flags != ALL_FRAMES)
+ && fh.error_resilient_mode
+ && enable_order_hint
+ {
+ for i in 0..NUM_REF_FRAMES {
+ fh.ref_order_hint[i] = r.0.read_bits::(order_hint_bits.try_into().unwrap())?;
+ if fh.ref_order_hint[i] != self.ref_info[i].ref_order_hint {
+ self.ref_info[i].ref_valid = false;
+ }
+ }
+ }
+
+ if fh.frame_is_intra {
+ self.parse_frame_size(&mut fh, &mut r)?;
+ Self::parse_render_size(&mut fh, &mut r)?;
+ if fh.allow_screen_content_tools > 0 && fh.upscaled_width == fh.frame_width {
+ fh.allow_intrabc = r.0.read_bit()?;
+ }
+ } else {
+ if !enable_order_hint {
+ fh.frame_refs_short_signaling = false;
+ } else {
+ fh.frame_refs_short_signaling = r.0.read_bit()?;
+ if fh.frame_refs_short_signaling {
+ fh.last_frame_idx = r.0.read_bits::(3)? as u8;
+ fh.gold_frame_idx = r.0.read_bits::(3)? as u8;
+ let ref_order_hints = self
+ .ref_info
+ .iter()
+ .map(|i| i.ref_order_hint)
+ .collect::>()
+ .try_into()
+ .unwrap();
+ self.set_frame_refs(&mut fh, &ref_order_hints)?;
+ }
+ }
+
+ let mut expected_frame_id = [0; REFS_PER_FRAME];
+ #[allow(clippy::needless_range_loop)]
+ for i in 0..REFS_PER_FRAME {
+ if !fh.frame_refs_short_signaling {
+ fh.ref_frame_idx[i] = r.0.read_bits::(3)?.try_into().unwrap();
+ }
+
+ if frame_id_numbers_present_flag {
+ /* DeltaFrameId */
+ let delta_frame_id =
+ r.0.read_bits::(delta_frame_id_length_minus_2 as usize + 2)? + 1;
+
+ if id_len == 0 {
+ return Err(format!("Invalid id_len {}", id_len));
+ }
+
+ let shifted_id_len = 1 << id_len;
+
+ expected_frame_id[i] =
+ (self.current_frame_id + shifted_id_len - delta_frame_id) % shifted_id_len;
+
+ let actual_frame_id = self.ref_info[fh.ref_frame_idx[i] as usize].ref_frame_id;
+
+ if expected_frame_id[i] != actual_frame_id {
+ return Err(format!(
+ "Invalid frame id, expected {} got {}",
+ expected_frame_id[i], actual_frame_id
+ ));
+ }
+ }
+ }
+
+ if fh.frame_size_override_flag && !fh.error_resilient_mode {
+ self.frame_size_with_refs(&mut fh, &mut r)?;
+ } else {
+ self.parse_frame_size(&mut fh, &mut r)?;
+ Self::parse_render_size(&mut fh, &mut r)?;
+ }
+
+ if fh.force_integer_mv > 0 {
+ fh.allow_high_precision_mv = false;
+ } else {
+ fh.allow_high_precision_mv = r.0.read_bit()?;
+ }
+
+ /* read_interpolation_filter */
+ fh.is_filter_switchable = r.0.read_bit()?;
+ if fh.is_filter_switchable {
+ fh.interpolation_filter = InterpolationFilter::Switchable;
+ } else {
+ fh.interpolation_filter = InterpolationFilter::try_from(r.0.read_bits::(2)?)?;
+ }
+
+ fh.is_motion_mode_switchable = r.0.read_bit()?;
+
+ if fh.error_resilient_mode || !self.sequence()?.enable_ref_frame_mvs {
+ fh.use_ref_frame_mvs = false;
+ } else {
+ fh.use_ref_frame_mvs = r.0.read_bit()?;
+ }
+
+ for i in 0..REFS_PER_FRAME {
+ let ref_frame = ReferenceFrameType::Last as usize + i;
+ let hint = self.ref_info[fh.ref_frame_idx[i] as usize].ref_order_hint;
+ fh.order_hints[ref_frame] = hint;
+
+ if !enable_order_hint {
+ fh.ref_frame_sign_bias[i] = false;
+ } else {
+ fh.ref_frame_sign_bias[i] = helpers::get_relative_dist(
+ enable_order_hint,
+ order_hint_bits,
+ hint.try_into().unwrap(),
+ fh.order_hint.try_into().unwrap(),
+ ) > 0;
+ }
+ }
+ }
+
+ if reduced_still_picture_header || fh.disable_cdf_update {
+ fh.disable_frame_end_update_cdf = true;
+ } else {
+ fh.disable_frame_end_update_cdf = r.0.read_bit()?;
+ }
+
+ if fh.primary_ref_frame == PRIMARY_REF_NONE {
+ Self::setup_past_independence(&mut fh);
+ } else {
+ /* load from the past reference */
+ let prev_frame =
+ &self.ref_info[fh.ref_frame_idx[fh.primary_ref_frame as usize] as usize];
+
+ if !prev_frame.ref_valid {
+ return Err("Reference is invalid".into());
+ }
+
+ /* load_loop_filter_params: load ref_deltas and mode_deltas */
+ fh.loop_filter_params.loop_filter_ref_deltas =
+ prev_frame.loop_filter_params.loop_filter_ref_deltas;
+ fh.loop_filter_params.loop_filter_mode_deltas =
+ prev_frame.loop_filter_params.loop_filter_mode_deltas;
+
+ /* load_segmentation_params: load feature_enabled and feature_data */
+ fh.segmentation_params.feature_enabled = prev_frame.segmentation_params.feature_enabled;
+ fh.segmentation_params.feature_data = prev_frame.segmentation_params.feature_data;
+ }
+
+ // TODO: we can live without this for now.
+ // if fh.use_ref_frame_mvs {
+ // // motion_field_estimators()
+ // }
+
+ self.parse_tile_info(&mut r, &mut fh.tile_info)?;
+ Self::parse_quantization_params(
+ &mut r,
+ &mut fh.quantization_params,
+ num_planes,
+ separate_uv_delta_q,
+ )?;
+ self.parse_segmentation_params(&mut r, &mut fh)?;
+ Self::parse_delta_q_params(&mut r, &mut fh.quantization_params)?;
+ Self::parse_delta_lf_params(
+ &mut r,
+ &mut fh.loop_filter_params,
+ fh.quantization_params.delta_q_present,
+ fh.allow_intrabc,
+ )?;
+
+ fh.coded_lossless = true;
+ for segment_id in 0..MAX_SEGMENTS {
+ let q_index = Self::get_qindex(&fh, true, segment_id as _);
+ let q = &fh.quantization_params;
+ fh.lossless_array[segment_id] = q_index == 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;
+ if !fh.lossless_array[segment_id] {
+ fh.coded_lossless = false;
+ }
+ if q.using_qmatrix {
+ if fh.lossless_array[segment_id] {
+ fh.seg_qm_level[0][segment_id] = 15;
+ fh.seg_qm_level[1][segment_id] = 15;
+ fh.seg_qm_level[2][segment_id] = 15;
+ } else {
+ fh.seg_qm_level[0][segment_id] = q.qm_y;
+ fh.seg_qm_level[1][segment_id] = q.qm_u;
+ fh.seg_qm_level[2][segment_id] = q.qm_v;
+ }
+ }
+ }
+
+ fh.all_lossless = fh.coded_lossless && fh.frame_width == fh.upscaled_width;
+ Self::parse_loop_filter_parameters(&mut r, &mut fh, num_planes)?;
+ Self::parse_cdef_params(&mut r, &mut fh, enable_cdef, num_planes)?;
+ Self::parse_loop_restoration_params(
+ &mut r,
+ &mut fh,
+ enable_restoration,
+ num_planes,
+ use_128x128_superblock,
+ subsampling_x,
+ subsampling_y,
+ )?;
+ Self::read_tx_mode(&mut r, &mut fh)?;
+ Self::parse_frame_reference_mode(&mut r, &mut fh)?;
+ self.parse_skip_mode_params(&mut r, &mut fh, enable_order_hint, order_hint_bits)?;
+
+ if fh.frame_is_intra || fh.error_resilient_mode || !enable_warped_motion {
+ fh.allow_warped_motion = false;
+ } else {
+ fh.allow_warped_motion = r.0.read_bit()?;
+ }
+
+ fh.reduced_tx_set = r.0.read_bit()?;
+ self.parse_global_motion_params(&mut r, &mut fh)?;
+ self.parse_film_grain_parameters(
+ &mut r,
+ &mut fh,
+ film_grain_params_present,
+ mono_chrome,
+ subsampling_x,
+ subsampling_y,
+ )?;
+
+ Self::skip_and_check_trailing_bits(&mut r, obu)?;
+
+ // See 5.10
+ if matches!(obu.header.obu_type, ObuType::Frame) {
+ r.byte_alignment()?;
+ }
+
+ fh.header_bytes = usize::try_from(r.0.position() / 8).unwrap();
+ Ok(fh)
+ }
+
+ fn parse_tile_group_obu<'a>(&mut self, obu: Obu<'a>) -> Result, String> {
+ let mut tg = TileGroupObu {
+ obu,
+ ..Default::default()
+ };
+
+ let mut r = Reader::new(tg.obu.as_ref());
+
+ if r.0.num_bits_left() % 8 != 0 {
+ return Err("Bitstream is not byte aligned".into());
+ }
+
+ let mut sz: u64 = r.0.num_bits_left() as u64 / 8;
+
+ let num_tiles = self.tile_rows * self.tile_cols;
+ let start_bit_pos = r.0.position();
+
+ if num_tiles > 1 {
+ tg.tile_start_and_end_present_flag = r.0.read_bit()?;
+ }
+
+ if num_tiles == 1 || !tg.tile_start_and_end_present_flag {
+ tg.tg_start = 0;
+ tg.tg_end = num_tiles - 1;
+ } else {
+ let tile_bits = (self.tile_cols_log2 + self.tile_rows_log2) as usize;
+ tg.tg_start = r.0.read_bits::(tile_bits)?;
+ tg.tg_end = r.0.read_bits::(tile_bits)?;
+ }
+
+ r.byte_alignment()?;
+
+ let end_bit_pos = r.0.position();
+ let header_bytes = (end_bit_pos - start_bit_pos) / 8;
+ sz -= header_bytes;
+
+ let mut tile_num = tg.tg_start;
+ while tile_num <= tg.tg_end {
+ let tile_row = tile_num / self.tile_cols;
+ let tile_col = tile_num % self.tile_cols;
+ let last_tile = tile_num == tg.tg_end;
+ let tile_size;
+
+ if last_tile {
+ tile_size = u32::try_from(sz).unwrap();
+ } else {
+ tile_size =
+ r.0.read_le::(self.tile_size_bytes.try_into().unwrap())? + 1;
+ sz -= u64::from(tile_size + self.tile_size_bytes);
+ }
+
+ let tile = Tile {
+ tile_offset: u32::try_from(r.0.position()).unwrap() / 8,
+ tile_size,
+ tile_row,
+ tile_col,
+ mi_row_start: self.mi_row_starts[tile_row as usize],
+ mi_row_end: self.mi_row_starts[tile_row as usize + 1],
+ mi_col_start: self.mi_row_starts[tile_col as usize],
+ mi_col_end: self.mi_row_starts[tile_col as usize + 1],
+ };
+
+ tg.tiles.push(tile);
+
+ // init_symbol, decode_tile() and exit_symbol() left to the accelerator.
+
+ // Skip the actual tile data
+ if tile_num < tg.tg_end {
+ r.0.skip_bits(tile_size as usize * 8)?;
+ }
+
+ tile_num += 1;
+ }
+
+ if tg.tg_end == num_tiles - 1 {
+ // left to the accelerator:
+ // if ( !disable_frame_end_update_cdf ) {
+ // frame_end_update_cdf( )
+ // }
+ // decode_frame_wrapup( )
+ self.seen_frame_header = false;
+ }
+
+ Ok(tg)
+ }
+
+ fn parse_frame_obu<'a>(&mut self, obu: Obu<'a>) -> Result, String> {
+ if !matches!(obu.header.obu_type, ObuType::Frame) {
+ return Err(format!(
+ "Expected a FrameOBU, got {:?}",
+ obu.header.obu_type
+ ));
+ }
+
+ let frame_header_obu = self.parse_frame_header_obu(&obu)?;
+ let obu = Obu {
+ header: obu.header,
+ data: match obu.data {
+ Cow::Borrowed(d) => Cow::Borrowed(&d[frame_header_obu.header_bytes..]),
+ Cow::Owned(d) => Cow::Owned(d[frame_header_obu.header_bytes..].to_owned()),
+ },
+ bytes_used: obu.bytes_used,
+ };
+ let tile_group_obu = self.parse_tile_group_obu(obu)?;
+
+ Ok(FrameObu {
+ header: frame_header_obu,
+ tile_group: tile_group_obu,
+ })
+ }
+
+ pub fn parse_frame_header_obu(&mut self, obu: &Obu) -> Result {
+ if !matches!(obu.header.obu_type, ObuType::FrameHeader | ObuType::Frame) {
+ return Err(format!(
+ "Expected a FrameHeaderOBU, got {:?}",
+ obu.header.obu_type
+ ));
+ }
+
+ if self.seen_frame_header {
+ Ok(self
+ .last_frame_header
+ .clone()
+ .take()
+ .ok_or::("Broken stream: no previous frame header to copy".into())?)
+ } else {
+ self.seen_frame_header = true;
+ let header = self.parse_uncompressed_frame_header(obu)?;
+ if header.show_existing_frame {
+ self.last_frame_header = None;
+ self.seen_frame_header = false;
+ } else {
+ /* TileNum = 0 */
+ self.seen_frame_header = true;
+ self.last_frame_header = Some(header.clone());
+ }
+
+ Ok(header)
+ }
+ }
+
+ /// Implements 7.20. This function should be called right after decoding a
+ /// frame.
+ pub fn ref_frame_update(&mut self, fh: &FrameHeaderObu) -> Result<(), String> {
+ // This was found as a bug otherwise by Nicolas Dufresne in GStreamer's
+ // av1parse.
+ if fh.show_existing_frame && !matches!(fh.frame_type, FrameType::KeyFrame) {
+ return Ok(());
+ }
+
+ if matches!(fh.frame_type, FrameType::IntraOnlyFrame) && fh.refresh_frame_flags == 0xff {
+ return Err("Intra-only frames cannot refresh all of the DPB as per the spec.".into());
+ }
+
+ let &SequenceHeaderObu {
+ color_config:
+ ColorConfig {
+ subsampling_x,
+ subsampling_y,
+ ..
+ },
+ film_grain_params_present,
+ bit_depth,
+ ..
+ } = self.sequence()?;
+
+ for (i, ref_info) in self.ref_info.iter_mut().enumerate() {
+ if ((fh.refresh_frame_flags >> i) & 1) != 0 {
+ ref_info.ref_valid = true;
+
+ ref_info.ref_frame_id = fh.current_frame_id;
+ ref_info.ref_frame_type = fh.frame_type;
+ ref_info.ref_upscaled_width = fh.upscaled_width;
+ ref_info.ref_frame_width = fh.frame_width;
+ ref_info.ref_frame_height = fh.frame_height;
+ ref_info.ref_render_width = fh.render_width;
+ ref_info.ref_render_height = fh.render_height;
+ ref_info.ref_order_hint = fh.order_hint;
+ ref_info.ref_mi_cols = self.mi_cols;
+ ref_info.ref_mi_rows = self.mi_rows;
+ ref_info.ref_subsampling_x = subsampling_x;
+ ref_info.ref_subsampling_y = subsampling_y;
+ ref_info.ref_bit_depth = bit_depth;
+ ref_info.segmentation_params = fh.segmentation_params.clone();
+ ref_info.global_motion_params = fh.global_motion_params.clone();
+ ref_info.loop_filter_params = fh.loop_filter_params.clone();
+ ref_info.tile_info = fh.tile_info.clone();
+ ref_info.display_frame_id = fh.display_frame_id;
+ ref_info.showable_frame = fh.showable_frame;
+
+ if film_grain_params_present {
+ ref_info.film_grain_params = fh.film_grain_params.clone();
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn highest_operating_point(&self) -> Option {
+ if self.operating_point_idc == 0 {
+ /* No scalability information, all OBUs must be decoded */
+ None
+ } else {
+ Some(helpers::floor_log2((self.operating_point_idc >> 8) as u32))
+ }
+ }
+
+ /// Fully parse an OBU.
+ pub fn parse_obu<'a>(&mut self, obu: Obu<'a>) -> Result, String> {
+ match obu.header.obu_type {
+ ObuType::Reserved => Ok(ParsedObu::Reserved),
+ ObuType::SequenceHeader => self
+ .parse_sequence_header_obu(&obu)
+ .map(ParsedObu::SequenceHeader),
+ ObuType::TemporalDelimiter => self
+ .parse_temporal_delimiter_obu()
+ .map(|_| ParsedObu::TemporalDelimiter),
+ ObuType::FrameHeader => self
+ .parse_frame_header_obu(&obu)
+ .map(ParsedObu::FrameHeader),
+ ObuType::TileGroup => self.parse_tile_group_obu(obu).map(ParsedObu::TileGroup),
+ ObuType::Metadata => Ok(ParsedObu::Metadata),
+ ObuType::Frame => self.parse_frame_obu(obu).map(ParsedObu::Frame),
+ ObuType::RedundantFrameHeader => Ok(ParsedObu::RedundantFrameHeader),
+ ObuType::TileList => Ok(ParsedObu::TileList),
+ ObuType::Reserved2 => Ok(ParsedObu::Reserved2),
+ ObuType::Reserved3 => Ok(ParsedObu::Reserved3),
+ ObuType::Reserved4 => Ok(ParsedObu::Reserved4),
+ ObuType::Reserved5 => Ok(ParsedObu::Reserved5),
+ ObuType::Reserved6 => Ok(ParsedObu::Reserved6),
+ ObuType::Reserved7 => Ok(ParsedObu::Reserved7),
+ ObuType::Padding => Ok(ParsedObu::Padding),
+ }
+ }
+}
+
+impl Default for Parser {
+ fn default() -> Self {
+ Self {
+ stream_format: StreamFormat::LowOverhead,
+ operating_point: Default::default(),
+ seen_frame_header: Default::default(),
+ last_frame_header: Default::default(),
+ operating_point_idc: Default::default(),
+ should_probe_for_annexb: true,
+ is_first_frame: Default::default(),
+ mi_cols: Default::default(),
+ mi_rows: Default::default(),
+ prev_frame_id: Default::default(),
+ current_frame_id: Default::default(),
+ ref_info: Default::default(),
+ mi_col_starts: [0; MAX_TILE_COLS + 1],
+ mi_row_starts: [0; MAX_TILE_ROWS + 1],
+ tile_cols_log2: Default::default(),
+ tile_cols: Default::default(),
+ tile_rows_log2: Default::default(),
+ tile_rows: Default::default(),
+ tile_size_bytes: Default::default(),
+ sequence_header: Default::default(),
+ }
+ }
+}
+
+impl Clone for Parser {
+ fn clone(&self) -> Self {
+ let sequence_header = self
+ .sequence_header
+ .as_ref()
+ .map(|s| Rc::new((**s).clone()));
+
+ Self {
+ stream_format: self.stream_format.clone(),
+ operating_point: self.operating_point,
+ seen_frame_header: self.seen_frame_header,
+ last_frame_header: self.last_frame_header.clone(),
+ operating_point_idc: self.operating_point_idc,
+ should_probe_for_annexb: self.should_probe_for_annexb,
+ is_first_frame: self.is_first_frame,
+ ref_info: self.ref_info.clone(),
+ mi_cols: self.mi_cols,
+ mi_rows: self.mi_rows,
+ prev_frame_id: self.prev_frame_id,
+ current_frame_id: self.current_frame_id,
+ mi_col_starts: self.mi_col_starts,
+ mi_row_starts: self.mi_row_starts,
+ tile_cols_log2: self.tile_cols_log2,
+ tile_cols: self.tile_cols,
+ tile_rows_log2: self.tile_rows_log2,
+ tile_rows: self.tile_rows,
+ tile_size_bytes: self.tile_size_bytes,
+ sequence_header,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::bitstream_utils::IvfIterator;
+ use crate::codec::av1::parser::{ObuAction, Parser, StreamFormat};
+
+ use super::ObuType;
+
+ /// Same as test-25fps.av1.ivf from Chromium
+ const STREAM_TEST_25_FPS: &[u8] = include_bytes!("test_data/test-25fps.ivf.av1");
+
+ /// Encoded with
+ ///
+ /// gst-launch-1.0 videotestsrc num-buffers=1 !
+ /// video/x-raw,format=I420,width=64,height=64 ! filesink
+ /// location=aom_input.yuv
+ ///
+ /// And:
+ ///
+ /// aomenc -p 1 --ivf -w 64 -h 64 -o av1-annexb.ivf.av1 aom_input.yuv --annexb=1
+ const STREAM_ANNEXB: &[u8] = include_bytes!("test_data/av1-annexb.ivf.av1");
+
+ #[test]
+ fn parse_test25fps() {
+ let mut parser = Parser::default();
+ let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
+ let mut num_obus = 0;
+
+ for packet in ivf_iter {
+ let mut consumed = 0;
+
+ while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
+ let obu = match obu {
+ ObuAction::Process(obu) => obu,
+ // This OBU should be dropped.
+ ObuAction::Drop(length) => {
+ consumed += usize::try_from(length).unwrap();
+ continue;
+ }
+ };
+ consumed += obu.bytes_used;
+ num_obus += 1;
+ }
+ }
+
+ // Manually checked with GStreamer under GDB by using a hitcount on
+ // "gst_av1_parse_identify_one_obu" *after* the stream format has been
+ // detected.
+ assert_eq!(num_obus, 525);
+ }
+
+ #[test]
+ /// Test that we can correctly identify streams in both "low-overhead" and
+ /// Annex B formats.
+ fn parse_annexb() {
+ let mut parser = Parser::default();
+ let mut ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
+ let packet = ivf_iter.next().unwrap();
+
+ parser.read_obu(packet).unwrap();
+ assert!(matches!(parser.stream_format, StreamFormat::LowOverhead));
+
+ let mut parser = Parser::default();
+ let mut ivf_iter = IvfIterator::new(STREAM_ANNEXB);
+ let packet = ivf_iter.next().unwrap();
+
+ parser.read_obu(packet).unwrap();
+ assert!(matches!(parser.stream_format, StreamFormat::AnnexB { .. }));
+ }
+
+ #[test]
+ /// Test that we can correctly identify streams in both "low-overhead" and
+ /// Annex B formats and identify all the OBUs in the stream until the end.
+ fn parse_annexb_full() {
+ let mut parser = Parser::default();
+ let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
+
+ for packet in ivf_iter {
+ let mut consumed = 0;
+
+ while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
+ let obu = match obu {
+ ObuAction::Process(obu) => obu,
+ // This OBU should be dropped.
+ ObuAction::Drop(length) => {
+ consumed += usize::try_from(length).unwrap();
+ continue;
+ }
+ };
+ assert!(matches!(parser.stream_format, StreamFormat::LowOverhead));
+ consumed += obu.bytes_used;
+ }
+ }
+
+ let mut parser = Parser::default();
+ let ivf_iter = IvfIterator::new(STREAM_ANNEXB);
+ let mut num_obus = 0;
+
+ for packet in ivf_iter {
+ let mut consumed = 0;
+
+ while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
+ let obu = match obu {
+ ObuAction::Process(obu) => obu,
+ // This OBU should be dropped.
+ ObuAction::Drop(length) => {
+ consumed += usize::try_from(length).unwrap();
+ continue;
+ }
+ };
+ assert!(matches!(parser.stream_format, StreamFormat::AnnexB { .. }));
+ consumed += obu.bytes_used;
+ num_obus += 1;
+ }
+ }
+
+ assert_eq!(num_obus, 3);
+ let annexb_state = match parser.stream_format {
+ StreamFormat::AnnexB(annexb_state) => annexb_state,
+ _ => panic!("Wrong StreamFormat, expected AnnexB"),
+ };
+ assert_eq!(
+ annexb_state.temporal_unit_consumed,
+ annexb_state.temporal_unit_size
+ );
+ assert_eq!(
+ annexb_state.frame_unit_consumed,
+ annexb_state.frame_unit_size
+ );
+ }
+
+ #[test]
+ fn parse_test25fps_obus() {
+ let mut parser = Parser::default();
+ let ivf_iter = IvfIterator::new(STREAM_TEST_25_FPS);
+
+ for packet in ivf_iter {
+ let mut consumed = 0;
+
+ while let Ok(obu) = parser.read_obu(&packet[consumed..]) {
+ let obu = match obu {
+ ObuAction::Process(obu) => obu,
+ // This OBU should be dropped.
+ ObuAction::Drop(length) => {
+ consumed += usize::try_from(length).unwrap();
+ continue;
+ }
+ };
+
+ let data_len = obu.bytes_used;
+
+ match obu.header.obu_type {
+ ObuType::SequenceHeader => {
+ parser.parse_sequence_header_obu(&obu).unwrap();
+ }
+ ObuType::FrameHeader | ObuType::RedundantFrameHeader => {
+ let fh = parser.parse_frame_header_obu(&obu).unwrap();
+ parser.ref_frame_update(&fh).unwrap();
+ }
+ ObuType::TileGroup => {
+ parser.parse_tile_group_obu(obu).unwrap();
+ }
+ ObuType::Frame => {
+ let frame = parser.parse_frame_obu(obu).unwrap();
+ parser.ref_frame_update(&frame.header).unwrap();
+ }
+ _ => {}
+ };
+
+ consumed += data_len;
+ }
+ }
+ }
+}
diff --git a/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/reader.rs b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/reader.rs
new file mode 100644
index 00000000..7daccfab
--- /dev/null
+++ b/crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/reader.rs
@@ -0,0 +1,251 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::bitstream_utils::BitReader;
+
+use crate::codec::av1::helpers;
+
+use super::parser::AnnexBState;
+
+pub(crate) struct Reader<'a>(pub BitReader<'a>);
+
+impl<'a> Reader<'a> {
+ pub fn new(data: &'a [u8]) -> Self {
+ Self(BitReader::new(data, false))
+ }
+
+ /// Implements uvlc(): Variable length unsigned n-bit number appearing
+ /// directly in the bitstream. See 4.10.3
+ pub fn read_uvlc(&mut self) -> Result {
+ let mut leading_zeroes = 0;
+ loop {
+ let done = self.0.read_bit()?;
+
+ if done {
+ break;
+ }
+
+ leading_zeroes += 1;
+ }
+
+ if leading_zeroes >= 32 {
+ return Ok(u32::MAX);
+ }
+
+ let value = self.0.read_bits::(leading_zeroes)?;
+ Ok(value + (1 << leading_zeroes) - 1)
+ }
+
+ /// Implements leb128(): Unsigned integer represented by a variable number
+ /// of little-endian bytes. See 4.10.5
+ pub fn read_leb128(&mut self) -> Result {
+ let mut value = 0u64;
+
+ for i in 0..8 {
+ let byte = u64::from(self.0.read_bits_aligned::(8)?);
+ value |= (byte & 0x7f) << (i * 7);
+
+ if byte & 0x80 == 0 {
+ break;
+ }
+ }
+
+ Ok(value as u32)
+ }
+
+ /// Implements su(n): Signed integer converted from an n bits unsigned
+ /// integer in the bitstream. (The unsigned integer corresponds to the
+ /// bottom n bits of the signed integer.). See 4.10.6
+ pub fn read_su(&mut self, num_bits: usize) -> Result {
+ let mut value: i32 = self
+ .0
+ .read_bits::(num_bits)?
+ .try_into()
+ .map_err(|_| String::from("Read more than 31 signed bits!"))?;
+ let sign_mask = 1 << (num_bits - 1);
+
+ if (value & sign_mask) != 0 {
+ value -= 2 * sign_mask;
+ }
+
+ Ok(value)
+ }
+
+ /// Implements ns(n): Unsigned encoded integer with maximum number of values
+ /// n (i.e. output in range 0..n-1). See 4.10.7
+ pub fn read_ns(&mut self, num_bits: usize) -> Result {
+ let w = helpers::floor_log2(num_bits as u32) + 1;
+ let m = (1 << w) - num_bits as u32;
+ let v = self.0.read_bits::(
+ usize::try_from(w).map_err(|_| String::from("Invalid num_bits"))? - 1,
+ )?;
+
+ if v < m.into() {
+ return Ok(v);
+ }
+
+ let extra_bit = self.0.read_bit()?;
+ Ok((v << 1) - u32::from(m) + u32::from(extra_bit))
+ }
+
+ /// Implements 5.9.13: Delta quantizer syntax.
+ pub fn read_delta_q(&mut self) -> Result {
+ let delta_coded = self.0.read_bit()?;
+
+ if delta_coded {
+ self.read_su(7)
+ } else {
+ Ok(0)
+ }
+ }
+
+ pub fn more_data_in_bitstream(&mut self) -> bool {
+ self.0.num_bits_left() > 0
+ }
+
+ pub(crate) fn consumed(&self, start_pos: u32) -> u32 {
+ (self.0.position() / 8) as u32 - start_pos
+ }
+
+ /// Get the length of the current OBU in AnnexB format.
+ pub fn current_annexb_obu_length(
+ &mut self,
+ annexb_state: &mut AnnexBState,
+ ) -> Result